<img src="https://cdn.jsdelivr.net/npm/kopscript@latest/assets/logo.png" width="96" height="96" alt="KopScript logo">

# KopScript

KopScript is a small, strongly-typed, object-oriented programming language that transpiles to
readable JavaScript. It's a hobby/learning project: a from-scratch lexer, parser, type
checker, and code generator, written in TypeScript — leaning deliberately toward C#
paradigms (type-first declarations, interfaces, explicit virtual/override dispatch,
auto-properties, enums) rather than TypeScript ones.

Because KopScript compiles to plain JavaScript and runs on Node, it runs identically on Windows,
macOS, and Linux — there's no native toolchain to maintain.

Generating KopScript with an AI coding assistant? Point it at **[`LLM.md`](./LLM.md)** —
a dense, complete, example-driven language spec designed to be loaded straight into an
LLM's context, as opposed to this README's narrative explanation.

## Highlights

- **Object-oriented, C#-flavored**: type-first declarations (`string Name;`, not `name: string`),
  classes with auto-properties, single inheritance plus interfaces via `class Dog : Animal, IPet`,
  explicit `virtual`/`override` dispatch (methods are sealed unless marked `virtual`), and a full
  `public`/`protected`/`private` access model enforced at compile time.
- **Strongly typed**: every declaration is explicitly typed and checked at compile time.
- **Generics**: one or more invariant type parameters on classes, interfaces, and free
  functions (`class Box<T> { public T Value; }`, `T Identity<T>(T x) { return x; }`), each
  optionally constrained to an interface (`class Box<T : IComparable> { ... }`), plus
  generic inheritance (`class IntBox : Box<number> { }`) — erased at codegen with zero
  runtime cost, the same way `task<T>`/`state<T>` already are.
- **Nullable types with compiler-enforced null-checking**: `T?` (`string?`, `Dog?`,
  `number[]?`) — a `T?` can't be used where a `T` is expected without an `if (x != null)`
  check first (checked statically, not just at runtime), and comparing a value that can
  never be null to `null` is itself a compile error.
- **Built-in string pattern matching**: a `match` expression over strings supporting literal,
  wildcard, and regex patterns — no `if`/`else` chains required.
- **Real multi-file programs**: `using "./shapes";` compiles a whole dependency graph
  together, checking `public`/`private` visibility across files and emitting genuine ES
  `import`/`export` statements — so a compiled KopScript module is also a normal JS module that
  a plain Node/TS project can `import` directly.
- **First-class functions and real closures**: lambdas (`(number x) => x * 2`), function
  types (`(number, number) => number`), and calling any function-valued expression — not
  just named functions — compiling straight to native JS arrow functions, so closures work
  exactly like they do in JS.
- **JS/npm interop**: `extern` declarations describe the shape of an existing JS function,
  class, or global value — ambient (`Math`, `document`, no import) or imported from a
  module specifier — so KopScript code can call real JS APIs, including a `virtual`
  method on an `extern class` that a real KopScript class can `override`, which is what
  lets a class subclass something from another package/repo entirely.
- **`async`/`await`**: `task<T>` (a promise of a `T`) compiles to a real JS `Promise`,
  `async`/`await` to the real thing, and `try`/`catch`/`finally`/`throw` give a rejected
  task something to catch.
- **Reactive state without RxJS**: `state<T>` is a small reactive box (`.Value` get/set,
  `.Subscribe((T) => void)`) for holding state and reacting to it changing — no
  Observables, no operators, no manual unsubscribe bookkeeping.
- **Real markup templates**: `template from "./x.html";` compiles a separate, almost-pure-HTML
  file — interpolation, event/property bindings, `*if`/`*for` — down to the exact same
  AST a hand-written `Render()` would produce, with auto-`Subscribe` wiring for state
  the markup reads — directly, or through the component's own methods. See [Templates](#templates).
- **Real debugging, not just readable output**: `ks build` emits a real source map next to
  every `.js` file (original `.ks` embedded, no separate file to ship), and `ks run` enables
  it automatically — an uncaught exception names the real `.ks` file and line, not the
  generated JS. Hand-rolled VLQ encoder, zero added dependencies. See "Source maps" below.
- **A real formatter**: `ks fmt` rewrites a file to one canonical style in place — no
  bikeshedding, no config to agree on. Comments survive it. Verified idempotent and
  meaning-preserving (byte-identical compiled output before/after) against every real `.ks`
  file in this repo and Kopular's own source. See "Formatter" below.
- **A companion framework, [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)**:
  components, constructor-injected services via a composition root (no DI container), and
  real-URL routing (no config DSL) — built entirely on the features above, in a separate
  repo consumed as a real package. [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)
  is a real app built on both.

## Getting started

```bash
npm install
npm run ks -- run examples/animals.ks
npm run ks -- run examples/classify.ks
npm run ks -- run examples/features.ks
npm run ks -- run examples/shapes.ks
npm run ks -- run examples/modules/main.ks
npm run ks -- run examples/closures.ks
npm run ks -- run examples/async.ks
npm run ks -- run examples/arrays.ks
```

These are all plain console scripts, runnable with Node directly. A browser-facing
KopScript project (DOM `extern` bindings, `ks watch` + a static file server for a live
reload-free dev loop) is a different setup — see
[Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular) and
[KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo) for a
real example of that workflow.

Or build to a `.js` file without running it:

```bash
npm run ks -- build examples/animals.ks
```

Run the test suite:

```bash
npm test
```

## Language tour

### Variables

Declarations are type-first, C# style — the type comes before the name, with no `let`:

```ks
number x = 5;
const string name = "Joe";
```

### Functions

Free functions are also type-first, with the return type before the name (no `function`
keyword — the same shape as a class method, just outside a class):

```ks
number Add(number a, number b) {
  return a + b;
}

void Announce(string message) {
  print(message);
}
```

### Functions as values, and closures

A function type is written `(ParamType, ...) => ReturnType`. Lambdas always have explicit
parameter types (no inference in v1) and never write their own return type — it's checked
against whatever function type the lambda is used where, which KopScript always knows concretely
since every declaration is explicitly typed:

```ks
number Apply((number) => number f, number x) {
  return f(x);
}

number Square(number x) { return x * x; }

print(Apply((number x) => x * 2, 5)); // 10 — an inline lambda
print(Apply(Square, 4));               // 16 — a named function used as a value
```

A lambda body can be an expression (`(number x) => x * 2`) or a block
(`(number x) => { return x * 2; }`). Lambdas are real closures — they capture their
enclosing scope by reference, compiling directly to JS arrow functions, so mutating a
captured variable from inside a lambda is visible to the code that captured it:

```ks
number counter = 0;
() => number next = () => { counter = counter + 1; return counter; };
print(next()); // 1
print(next()); // 2
```

Calling any expression whose type is a function type works — a variable, a field, a call
that returns a function, not just a named function or method.

### Classes, auto-properties, and inheritance

```ks
class Animal {
  public string Name { get; set; }
  constructor(string name) { this.Name = name; }
  public virtual string Speak() { return this.Name + " makes a sound"; }
}

class Dog : Animal {
  public override string Speak() { return this.Name + " barks"; }
}
```

- `public string Name { get; set; }` is a read-write auto-property; `public string Name { get; }`
  is get-only — it can only be assigned within its own class's constructor (like a C#
  read-only auto-property), and is rejected anywhere else, including other methods of the
  same class.
- `public` / `protected` / `private` are enforced at compile time: `private` members are
  only reachable from within the declaring class (any instance, not just `this`);
  `protected` members are reachable from the declaring class and its subclasses; `public`
  is unrestricted. Fields default to `public` if no modifier is written.
- Methods are sealed by default, like C#. A subclass may only redefine a method that the
  base class marked `virtual`, and must mark its own version `override`; the compiler
  rejects a redefinition that omits `override`, an `override` with no matching `virtual`
  base method, or a signature mismatch between the two.
- `static` fields and methods belong to the class itself, accessed as `ClassName.Member`
  (never through an instance or `this`) and shared across every instance. A static field
  requires an inline initializer, since there's no constructor to assign it in:
  `private static number Count = 0;`. `static` cannot be combined with `virtual`/`override`,
  and static auto-properties aren't supported in v1 — use a static field instead.
- A subclass that doesn't declare its own constructor inherits the superclass's (the same
  way JavaScript's `extends` works). A subclass that does declare one calls the base
  constructor explicitly, via `: base(...)`:
  ```ks
  class Animal { constructor(string name) { ... } }
  class Dog : Animal {
    constructor(string name, string breed) : base(name) { ... }
  }
  ```

### Interfaces

`class Foo : Base, IBar, IBaz` mixes at most one base class with any number of interfaces
in a single colon-separated list — the compiler figures out which name is which. A class
implementing an interface must provide every method the interface declares, with a
matching signature. Interfaces can themselves extend other interfaces
(`interface INamedShape : IShape { ... }`), which pulls in the parent's method
requirements too; implementing classes must satisfy the whole chain. Interfaces only
declare method signatures in v1 — no properties.

```ks
interface ISpeaker {
  string Speak();
}

class Dog : ISpeaker {
  public string Speak() { return "Woof"; }
}

void Announce(ISpeaker s) {
  print(s.Speak());
}
```

### Generics

Classes and interfaces can take one or more type parameters:

```ks
class Box<T> {
  public T Value;
  constructor(T v) { this.Value = v; }
  public T Get() { return this.Value; }
}

Box<number> nb = new Box<number>(5);
Box<string> sb = new Box<string>("hi");
print(nb.Get());   // 5

class Pair<K, V> {
  public K First;
  public V Second;
  constructor(K k, V v) { this.First = k; this.Second = v; }
}

Pair<number, string> p = new Pair<number, string>(1, "a");
```

The type argument(s) are required everywhere the generic type is named — on the variable's
declared type *and* separately on `new` (`Box<number> nb = new Box<number>(5);`, not
just one or the other) — and the count must match the declaration exactly (`Pair<number>`
alone, or `Pair<number, string, bool>`, are both arity errors). Generics are erased at
codegen exactly like `task<T>`/`state<T>` already are — `Box<number>` and `Box<string>`
compile to the identical plain `class Box`, so there's no runtime cost.

Still a deliberately small slice of generics, not a scaled-down promise of more later
inside v1:

- **Invariant.** `Box<Dog>` is **not** assignable to `Box<Animal>` even though `Dog :
  Animal`, and `Pair<number, string>` is not assignable to `Pair<string, number>` — every
  slot must match exactly, in order.

By default a type parameter is fully unconstrained, so you can't call any member on a bare
`T`/`K`/`V` value inside the generic class's own body — the same restriction C#/Java/
TypeScript put on an unconstrained parameter, not a bug. A single optional `: IFoo`
constraint per parameter lifts that:

```ks
interface IComparable { number CompareTo(); }
class Box<T : IComparable> {
  public T Value;
  constructor(T v) { this.Value = v; }
  public number Compare() { return this.Value.CompareTo(); } // legal — T is constrained
}
Box<Money> b = new Box<Money>(new Money(5));   // Money must implement IComparable
```

The constraint must be an interface (never a class), and a type argument satisfies it by
implementing it directly, through an ancestor, or (inside another generic body) by already
being an equally-or-more-constrained type parameter of its own. **v1 limit**: at most one
constraint per parameter — `T : IFoo, IBar` isn't supported.

A class or interface *can* extend/implement a generic base, type arguments and all:

```ks
class IntBox : Box<number> { }                   // concrete: threads a fixed type through
class Container<T> : Box<T> {                     // generic: threads its OWN T through
  public T GetAgain() { return this.Get(); }
}
Container<number> c = new Container<number>(5);
print(c.GetAgain());                               // 5, and c.Value/c.Get() work too
```

Member lookup (fields, methods, an inherited constructor) resolves correctly however many
generic-base links up the chain a member was actually declared, substituting all the way
down. **v1 limit**: at most one generic entry across a whole base list (the superclass, or
one implemented interface — not several at once) — `class Foo<T> : Box<T>, IContainer<T>`
is a compile error even though each half would work alone.

**Free functions can be generic too** — only free/top-level functions, not class methods
(a method inside a class still only uses its enclosing class's own type parameters, never
introduces a new one):

```ks
T Identity<T>(T x) { return x; }
number n = Identity(5);       // T = number, inferred from the argument
string s = Identity("hi");    // T = string, independently
```

**No explicit type argument at the call site** — `Identity<number>(5)` isn't valid syntax;
`T` is always inferred from the actual argument types. This is a real grammar constraint,
not a missing feature: `new Box<number>(...)` can disambiguate `<` from a comparison only
because `new` is a distinct keyword context — a bare call has no such anchor, so
`f<T>(x)` would be genuinely ambiguous with a chained `<`/`>` comparison. If inference
can't pin down a type parameter from the arguments given, that's a compile error naming the
unresolved parameter, not a syntax feature to reach for.

See `LLM.md`'s "Generics" section for the exhaustive rules if you're generating code
against this — nested generics (`Box<Box<number>>`, `Box<number>?[]`, `Pair<number,
Pair<string, bool>>`) and generic interfaces used as standalone parameter types both work
and are covered there.

### Enums

```ks
enum Color { Red, Green, Blue }

Color c = Color.Green;
if (c == Color.Green) { print("It's green"); }
```

Members are numbered from `0` in declaration order, compiling to a frozen JS object.

An enum is also a valid `match` subject (`match c { Color.Red => ..., Color.Green =>
..., Color.Blue => ..., _ => ... }`, patterns naming its own members) — covering
every member lets the trailing `_` be omitted, with a real compile error if one is
missing and there's no `_` to fall back on. See LLM.md's "`match` expression" section
for the full exhaustiveness rules.

### Modules

`using "./shapes";` brings every `public` top-level declaration from that file (resolved
relative to the current file, no `.ks` extension) into unqualified scope — there's no
namespace prefix and no picking individual names, the same way a C# `using` directive
brings a whole namespace into scope. `using` directives must be a contiguous block at the
very top of the file, and aren't transitive — each file lists every file (or package) whose
names it references, not just its direct dependency's dependencies.

A bare package name — `using "kopular";` — resolves an installed npm package's own
KopScript declarations instead: `node_modules/<name>/package.json`'s `"kopscript"` field
names a `.ks` file (typically a set of `extern` declarations for that package's modules),
searched for in `node_modules` the way Node itself resolves a package, walking up parent
directories if needed. Those declarations come into scope the same way a relative `using`'s
do, but the file itself is never compiled — each consuming file's own output imports only
the specific bindings it references, straight from each extern's own `from` module. See
"Interop with JS/npm (`extern`)" below for what a package's own declarations file looks
like from the other side.

```ks
// shapes.ks
interface IShape {
  number Area();
}

class Circle : IShape {
  public number Radius;
  constructor(number radius) { this.Radius = radius; }
  public number Area() { return this.Radius * this.Radius * 3; }
}
```

```ks
// main.ks
using "./shapes";

Circle c = new Circle(2);
print(c.Area());
```

Every top-level class, interface, enum, and function is `public` (exported, visible to
files that `using` this one) by default — mark it `private` to keep it file-scoped:

```ks
private number Helper() { return 42; } // not visible outside this file
class Public { }                        // visible by default
```

Top-level `let`/`const` variables are never exportable — only types and functions cross
file boundaries. There's also a structural rule worth knowing: if a class or interface is
exported, everything in its public surface must be exported too (an exported class's base
class and implemented interfaces, an exported interface's base interfaces) — the compiler
rejects `public class Derived : Base` if `Base` isn't also `public`, since otherwise a
file importing `Derived` would have no way to make sense of its own base type.

`ks build`/`ks run` compile the entry file plus everything it transitively `using`s,
each to its own `.js` file with real ES `import`/`export` statements — so `node
main.js` (or a plain JS/TS project importing the compiled output directly) just works,
with Node's own module resolution doing the wiring.

### Interop with JS/npm (`extern`)

`extern` declarations describe the shape of something that already exists in JS, without
providing a KopScript implementation — KopScript trusts the declared types (same trust model as a
TypeScript `.d.ts` file). There are four forms, and each can be either **ambient** (no
`from` — an already-existing global like `Math` or `document`, nothing to import) or
**imported** (`from "<module>"` — a named export from an npm/Node module):

```ks
extern string ReadFileSync(string path) from "node:fs" as "readFileSync"; // function
extern Document document;                                                 // value (ambient)

extern class Element {          // class — a type shape, describing an existing class
  constructor();
  string textContent { get; set; }
  void addEventListener(string eventType, (Event) => void handler);
  static number InstanceCount { get; }
} from "some-dom-lib";

extern class Router : Component { void Navigate(string path); } from "some-package"; // : Base — inherits Component's members

extern enum Status { Loading, Success, Failure } from "some-package"; // a real enum, described
```

`as "<jsName>"` maps a KopScript-facing name to the real JS-side identifier (defaults to the
declared name if omitted) — needed constantly in practice, since JS naming (`camelCase`,
`readFileSync`) rarely matches KopScript's (`PascalCase`, `ReadFileSync`). Extern **class
members** have no separate rename mechanism, though — write them with the exact real JS
name (`addEventListener`, not `AddEventListener`), since that's what actually exists at
runtime. Calling an extern method or accessing an extern property compiles exactly like
any other member access — no special codegen, since it isn't reimplementing the class,
just describing one that's already there.

**`extern class Name : Base { ... }`** inherits `Base`'s members and is assignable to it —
`Base` must itself be a declared class (real or extern); an unknown name is `KS4104`. No
modeled inheritance *between two unrelated* extern classes beyond this (each stands alone
unless one names the other as its base).

An **`extern enum`** describes a real enum declared in another module — `Name.Member`
access and `match` exhaustiveness both work exactly like a locally-declared enum, since
it's registered the same way underneath. Member order must match the real enum's own
declaration order (this is trusted, not verified against the actual JS object — same
"KopScript trusts the declared shape" model as every other `extern`).

`from "<module>"` isn't only for npm packages — a relative path (`"./foo"`, `"../foo"`)
names a same-project, hand-written `.js` file (not compiled from `.ks`), the same way
`using` names a same-project `.ks` file; the compiler appends `.js` automatically if you
leave it off (real Node ESM requires the extension for a relative path; a bare package
specifier is left untouched, since package resolution needs no extension). This is the
real, working way to reach a genuine JS capability KopScript's own syntax has no
expression for — a dynamic `import()` for real lazy-loading/code-splitting being the main
one: hand-write a tiny loader, `extern`-declare a function matching its shape, and its
target module is only ever fetched the first time that function actually runs:
```js
// page_loader.js — hand-written, not compiled
export async function LoadPage() {
  const { Page } = await import("./page.js");
  return new Page();
}
```
```ks
extern task<Page> LoadPage() from "./page_loader";
Page p = await LoadPage();
```

A real, working example: [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)'s
`dom.ks` declares ambient bindings for `document`/`Element`/`Event`/`window`/`location`,
which its `Component`/`Router` are built on — and
[KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)
consumes `Component`/`Router` themselves via `extern class ... from "kopular/...";`,
proving `extern` works as a real cross-*package* boundary, not just for describing DOM
globals within a single project.

An `extern class` can carry its own type parameter(s), optionally constrained —
`extern class Box<T> { ... }`, `extern class Pair<K, V> { ... }`, `extern class Box<T :
IComparable> { ... }` — exactly the same rules as a real generic class (see "Generics"
above: invariant, erased, and a real class can extend it as a generic base), so a generic
type from another package (e.g. Kopular's `FormField<T>`) can be described and
instantiated generically, not just per concrete type:

```ks
extern class Box<T> {
  constructor(T v);
  T Value { get; }
} from "some-package";

Box<number> nb = new Box<number>(5);
```

An extern *free function* can be generic the same way — `extern (T) => string?
CombineValidators2<T>((T) => string? v1, (T) => string? v2) from "kopular/forms";`
re-describes a real generic free function from another package, with the same
inference/constraints a local one gets.

Now that KopScript has `async`/`await` and `task<T>` (see below), a Promise-based JS API is
describable too — `extern task<string> Fetch(...) from "..." as "fetch";` is legitimate,
and `await`ing it works exactly like awaiting any other KopScript task. What's still not cleanly
describable is old-style Node **callback-based** async that doesn't return a Promise at all
(`fs.readFile(path, callback)` with an error-first callback) — nothing in KopScript understands
that specific convention, even though the callback parameter itself is describable as an
ordinary function type.

### Compile-time file embedding (`raw`)

`raw string <Name> from "<path>";` reads the file at `<path>` (resolved relative to the
`.ks` file that declares it) at **compile time** and embeds its contents as a plain string
constant — no `fetch`, no runtime file access, nothing left over at all once compiled.
Top-level only, always `string`, exported by default like any other top-level declaration
(`private` keeps it file-scoped):

```ks
raw string CounterHtml from "./counter.html";

void Main() {
  print(CounterHtml); // the file's exact contents, as a string
}
```

Unlike `extern`, there's no real JS export being described here — the compiler fabricates
the value itself — so there's no `as "jsName"` clause. This is a general-purpose,
compile-time-only file embedding primitive — useful for anything that wants a file's
exact contents as a string constant with no runtime file access. (If what you actually
want is markup that becomes real DOM construction code, not just a string — see
[Templates](#templates) below, which is the more specific answer for that case.)

### async/await, task&lt;T&gt;, and try/catch

`task` (a promise of nothing) and `task<T>` (a promise of a `T`) are the one hardcoded
parametrized type in v1 — not general generics, just enough to give `async`/`await` a
return type. `async` requires (and is required by) a `task`/`task<T>` return type; `return`
statements inside an async body are checked against the unwrapped result type, exactly
like real C#/JS async functions — you write `return 5;`, not `return SomeTaskOf(5);`:

```ks
async task<number> Double(number x) {
  return x * 2;
}

async task<number> Chain(number x) {
  number a = await Double(x);
  number b = await Double(a);
  return b;
}
```

Both compile directly to their real JS equivalents (`async function`, `await`), so an
`async` KopScript function returns a genuine `Promise` and composes with plain JS/TS code
without any wrapping. `await` also works at the top level of a file, matching real
top-level await in an ES module.

Exception handling exists specifically so a rejected task has something to catch:

```ks
try {
  number result = await MightFail(x);
  print(result);
} catch (string message) {
  print("caught: " + message);
} finally {
  print("done");
}
```

`throw` accepts any type — there's no base exception/error type to require conformance
to, matching JS's own looseness — and a `catch (Type name)` parameter's type is trusted,
not verified (the same trust model as `extern`: KopScript has no way to know what a given `throw`
site might actually produce). At least one of `catch`/`finally` is required; a bare `try {}`
alone is rejected. `examples/async.ks` and `test/codegen.test.ts` exercise a full chain of
async calls, a caught error, and a `finally` that runs on both the success and failure path.

Known v1 limitations: no async lambdas (`await` is never valid inside a lambda body, even
inside an async function — only free functions and methods can be `async`), and no way to
construct a `task` value directly outside of an `async` function body.

### Reactive state: state&lt;T&gt;

`state<T>` is the other hardcoded parametrized type in v1, alongside `task<T>` — a reactive
box holding a `T`. Construct one with `state(initial)` (no explicit type argument; `T` is
inferred from `initial`), read/write it through `.Value`, and register a listener with
`.Subscribe((T) => void)` that fires every time `.Value` is assigned:

```ks
state<number> count = state(0);
() => void unsubscribe = count.Subscribe((number v) => print("now " + v));
count.Value = count.Value + 1; // prints "now 1"
unsubscribe();
count.Value = count.Value + 1; // prints nothing — the listener is gone
```

This exists to give Kopular components a way to re-render on state change without pulling in
anything like RxJS — no Observables, no operators. `Subscribe` returns an unsubscribe
handle for the cases that need one (something dynamically created and destroyed releasing
its subscription when it goes away), but most subscribers don't need it at all: a
component subscribes once, in its constructor, to call its own `Update()`, and simply
discards the returned handle since it lives as long as the app does:

```ks
class Counter : Component {
  private state<number> Count;

  constructor() : base() {
    this.Count = state(0);
    this.Count.Subscribe((number v) => this.Update());
  }

  public override VElement Render() {
    VElement button = VElement.Create("button");
    button.TextContent = "Count: " + this.Count.Value;
    button.OnClick = (Event e) => {
      this.Count.Value = this.Count.Value + 1; // Update() fires automatically
    };
    return button;
  }
}
```

Compare this to the pre-`state<T>` version of the same component, which had to call
`this.Update();` by hand inside every single event handler that touched state — one
`Subscribe` call at construction now does that job everywhere.
[Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)'s `Component`
is built around exactly this pattern, and its own README covers the rest of the
"Angular's separation, none of the bloat" story — constructor-injected services instead
of a DI container, and hash-based routing instead of a config DSL — both built entirely
on ordinary KopScript, no further compiler features required beyond `state<T>` and the
`extern`/`virtual` support above.

Hand-writing `Render()` like this, statement by statement, is still fully supported — but
a Kopular component today more commonly expresses it as a **template** instead. See
[Templates](#templates) below for the markup-based alternative to this same method, and
for how `Count.Subscribe(...)` above can often be skipped entirely.

### Templates

`template from "<path>";` inside a class body replaces a hand-written `Render()` with a
real, separate markup file — almost pure HTML, with data bindings and a small set of
structural directives standing in for the imperative DOM code above:

```ks
class Counter : Component {
  public state<number> Count;
  constructor() : base() { this.Count = state(0); }
  public void Increment() { this.Count.Value = this.Count.Value + 1; }
  template from "./counter.html";
}
```

```html
<!-- counter.html -->
<button (click)="Increment()">Count: {{ Count.Value }}</button>
```

This compiles to exactly the `Render()` method you'd otherwise write by hand — the
template compiler is a pass that runs between parsing and type-checking, turning the
markup into ordinary `MethodDecl`/statement/expression AST nodes and splicing the result
into the class before checking ever runs. There's no separate runtime template engine and
no interpreted expression language: `{{ Count.Value }}` and `(click)="Increment()"`
contain real KopScript, parsed and type-checked exactly like anything else in the file,
with errors reported at their real position in the `.html` file, not the `.ks` file. The
compiler itself does no diffing — it just builds a `VElement` tree as data; Kopular's own
`Component` base class is what diffs that tree against the previous render and patches
real DOM (see Kopular's own README/CHANGELOG for that engine).

A class may have a `template` or a hand-written `Render()`, never both — that's a compile
error. `ks watch` also tracks the referenced `.html` file, so editing markup alone
triggers a rebuild.

Supported bindings and directives:

| Syntax                      | Desugars to                                              |
| --------------------------- | --------------------------------------------------------- |
| `{{ expr }}` (in text)       | `el.TextContent = $"...{expr}...";` (an `InterpolatedStringLiteral`, same as `$"..."`) |
| `(event)="stmt"`             | `el.OnEvent = (Event e) => { stmt };` — `event` must be one of `click`/`input`/`blur`/`change`, `VElement`'s own fixed set of named event fields |
| `[prop]="expr"`              | `el.Prop = expr;` for `id`/`className`/`value` (`VElement`'s own named fields); `el.SetAttr("prop", expr);` for anything else |
| `[(value)]="Field"`          | sugar for `[value]="Field"` + `(input)="Field = e.target.value"` — see below |
| `class="..."` (static)       | `el.ClassName = "...";` (aliased, since `class` is a KopScript keyword) |
| `*if="expr"`                 | a real `if (expr) { ... }` around the element's creation |
| `*for="Type var of expr"`    | a real `for (Type var in expr) { ... }` — the element type is explicit, matching KopScript's no-inference stance elsewhere (Generics, Nullable types) |
| `*mount="expr"`               | `VElement.Mount(expr)` — embeds a live child component; see below |

**Two-way binding**: `[(value)]="Field"` (a template-only sugar — there's no equivalent
shorthand for a hand-written `Render()`, which already has direct field/handler access)
desugars to exactly the pair you'd otherwise write by hand: a `[value]` property binding
plus an auto-generated `(input)` handler assigning back. Restricted to `value`
specifically — the one `VElement` field a user can change through direct interaction
(typing, picking an option); `[(id)]`/`[(className)]` are compile errors (`KS5017`), since
neither has an equivalent "user just changed this" event. The bound expression must be a
simple field reference — a bare name, `this.Field`, or a deeper member-access path like
`Field.Value` (the way to two-way-bind into a `state<T>`'s own settable `.Value`, e.g. for
a `Computed2` source) — `[(value)]="Field.Trim()"` is a compile error (`KS5018`), since a
method call has nothing sensible to assign back into.

**Auto-subscribe**: a `state<T>` field declared on the component gets its
`Subscribe((v) => this.Update())` wired up automatically when the template reads it —
directly (like `Count` above), or through the component's own methods that a render-time
binding calls, followed transitively (`*for="Todo t of Visible()"` subscribes to whatever
`Visible()` reads). Event-handler bindings aren't followed, and state that's only assigned
during render isn't subscribed, so a render can't trigger itself. It's a syntactic walk of
the class's own fields and methods, not a type-checker query, so state on *another* object
— `this.SomeService.Count` — still needs a manual `Subscribe`.

**Embedding a live child component**: `*mount="expr"` embeds an already-built `Mountable`
(most commonly a `Component` field, or a `*for` loop variable) in place of the element, via
`VElement.Mount` — the exact same mechanism a hand-written `Render()` uses (see Kopular's
own README, "Nested component composition", for what `Mountable`/`VElement.Mount` are).
Unlike `*if`/`*for`, `*mount` isn't a structural directive they're mutually exclusive with —
it composes with either, and the headline case is doing exactly that:

```html
<p *mount="Badge"></p>
<li *for="Row r of Rows" *mount="r"></li>
```

The template doesn't construct the mounted value or pass it props/constructor arguments —
whatever `*mount` names must already be a real, fully-built `Mountable` by the time the
template reaches it; how it got built (a field initializer, `Pure DI`, a loop) stays
ordinary KopScript outside the template. The element's own tag and attributes are
discarded — nothing about a `*mount` slot is a real DOM node with content of its own, that's
entirely the mounted child's own `Render()` to own — except `id`/`[id]`, which still sets
`VElement.Id` for list-keying, same as any other child. Anything else on the same element
(another static attr, a prop/event/two-way binding, children) is a compile error rather
than a silent no-op, since none of it has anywhere to go — reported as `KS3004` (every
template-body error is re-anchored to the `.ks` file's own `template from` declaration and
wrapped under `KS3003`/`KS3004`; the underlying `KS5020`/`KS5021` check still runs, but
isn't the code you'll actually see — match on the message text instead).

v1 cuts, same discipline as generics and nullable types: exactly one top-level element per
template (no auto-wrapping — a clear error instead); no pipes, no
stacking two of `*if`/`*for` on one element, and no way to pass a `*mount`ed child props or
projected content from the template itself (it must already be fully built).

**Scoped component styles**: a class-body `styles from "<path>.css";` scopes a real
stylesheet so it only matches elements that class itself renders — independent of
`template from`, and works with a hand-written `Render()` too:

```ks
class Widget : Component {
  constructor() : base() { }
  template from "./widget.html";
  styles from "./widget.css";
}
```

Every element `template from` builds gets a `data-kop-scope="<id>"` attribute
automatically (`<id>`: the class name plus a short compile-time hash — stable within one
build, not user-controlled). The referenced `.css` file is rewritten at compile time so
every selector requires that same attribute — every compound in a combinator chain, not
just the last (`.a .b` → `.a[data-kop-scope="Widget-4f2a1c"] .b[data-kop-scope="Widget-4f2a1c"]`,
so a descendant combinator can't leak into an unrelated scoped subtree) — and spliced into
the constructor as one `ScopedStyles.Inject(id, rewrittenCss);` call (Kopular's `vdom.ks` —
idempotent, injected once per component *type* into `document.head`, not once per
instance, exactly like Angular's own `_ngcontent-xxx` attributes are per-class too). A
hand-written `Render()` gets no automatic attribute injection (the same asymmetry as
`[(value)]`/`*mount` — template sugar only), but the class still gets a
`protected string ScopeId;` field set for it to apply manually:
`el.SetAttr("data-kop-scope", this.ScopeId);`. `styles from` on a class with no
constructor is a compile error (`KS3012`) — there's nowhere to splice the injection call.

CSS parsing (`src/css_scoping.ts`) is a real tokenizer — works on minified/single-line
input, handles comments and quoted strings correctly — with the same v1 cuts Angular's own
`ViewEncapsulation.Emulated` accepts, not shortcuts invented here: no CSS nesting (`&`); no
`:host`/`:host-context` (not needed — a component's own template root goes through the
exact same path as its descendants, so an ordinary selector already reaches it);
`:not()`/`:is()`/`:where()` arguments aren't recursively rewritten (the whole compound,
argument included, is treated as one opaque unit); `@import`ed stylesheets are never
fetched/rewritten; `@keyframes` names are global, not renamed (can collide across
components); any at-rule besides `@media`/`@supports` (which recurse, rewriting inner
selectors) or `@keyframes`/`@font-face` (copied verbatim) — `@page`, `@layer`, future
syntax — is copied through completely untouched.

**A deliberate layering note**: the `template from` syntax lives in kopscript's own
grammar (Kopular can't extend a language it doesn't own), but what it desugars *to* —
`VElement.Create`, `.AppendChild`, `.TextContent`, `.SetAttr`, the named `On*` event
fields — assumes exactly the surface
[Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)'s
`velement.ks` declares. That's a real coupling from the compiler to one specific consumer,
accepted deliberately rather than building a generic pluggable desugaring-target system
for a hypothetical second framework that doesn't exist today. If one ever does, that's the
point to generalize this.

`raw string <Name> from "./x.html";` (above) still exists as a general-purpose
compile-time file embedding feature — but for the specific "component markup in its own
file" use case, `template from` is the real answer; `raw` no longer needs to stand in for
it.

### String interpolation

```ks
string name = "Joe";
string msg = $"Hello, {name}!";
```

### Pattern matching over strings

The headline feature: a `match` expression with literal, comma-separated, and regex patterns.
A `match` must end with a `_` wildcard arm.

```ks
string Classify(string input) {
  return match input {
    "cat", "dog" => "animal",
    r"^[0-9]+$" => "number",
    _ => "unknown"
  };
}
```

### String stdlib

KopScript's `string` type exposes PascalCase members that map directly onto
`String.prototype`:

| KopScript                    | JavaScript                |
| ----------------------- | -------------------------- |
| `s.Contains(x)`        | `s.includes(x)`           |
| `s.StartsWith(x)`      | `s.startsWith(x)`         |
| `s.EndsWith(x)`        | `s.endsWith(x)`           |
| `s.Replace(a, b)`      | `s.replaceAll(a, b)`      |
| `s.Split(x)`           | `s.split(x)`              |
| `s.Trim()`             | `s.trim()`                |
| `s.ToUpper()`          | `s.toUpperCase()`         |
| `s.ToLower()`          | `s.toLowerCase()`         |
| `s.CompareTo(x)`       | `s.localeCompare(x)`      |
| `s.Length`             | `s.length`                |

A string literal can use either `"double"` or `'single'` quotes — identical in every way
(both interpret `\n`/`\t`/`\\`/etc.), so pick whichever the other kind of quote *isn't*, e.g.
a literal inside a template attribute already delimited by `"`.

### Array stdlib

Arrays expose `.Length`, plus `Map`/`Filter`/`ForEach`/`Push`/`Find`/`FindIndex`/
`Includes`/`IndexOf`/`Sort`/`Reverse`/`Slice`/`Concat`/`Join`/`Reduce`, using lambdas or
any other function-valued expression (a named function, a variable holding one, ...):

```ks
number[] xs = [1, 2, 3, 4];
string[] labels = xs.Map((number x) => "n" + x);      // ["n1", "n2", "n3", "n4"]
number[] evens = xs.Filter((number x) => x % 2 == 0);  // [2, 4]
xs.ForEach((number x) => print(x));
number[] grown = xs.Push(5);                           // [1, 2, 3, 4, 5]; xs itself is untouched

number? found = xs.Find((number x) => x > 2);          // 3, or null if nothing matches
number idx = xs.FindIndex((number x) => x > 2);        // 2, or -1 if nothing matches
bool has = xs.Includes(3);                              // true
number at = xs.IndexOf(3);                              // 2, or -1 if not present

number[] sorted = xs.Sort((number a, number b) => a - b);  // new array; xs untouched
number[] rev = xs.Reverse();                                // new array; xs untouched
number[] mid = xs.Slice(1, 3);                              // [2, 3]
number[] joined = xs.Concat([5, 6]);                         // [1, 2, 3, 4, 5, 6]
string csv = xs.Join(", ");                                 // "1, 2, 3, 4"

number sum = xs.Reduce((number acc, number x) => acc + x, 0);  // 10
```

`Map`/`Filter`/`ForEach`/`Find`/`FindIndex`/`Includes`/`IndexOf`/`Slice`/`Concat`/`Join`/
`Reduce` compile straight to their real `Array.prototype` equivalents. `Push`/`Sort`/
`Reverse` are the departures from JS: all three are **non-mutating** (return a new array;
the original is unchanged) — unlike JS's own `push`/`sort`/`reverse`, which all mutate in
place — chosen for consistency and because nothing else in KopScript's type system models
aliasing/mutable-reference semantics, so a silently-mutating method would be a surprising
outlier. `Sort`/`Reverse` compile to a spread-copy first (`[...xs].sort(...)`), not a
direct `.sort()`/`.reverse()` call. `Sort`'s comparator is always required (no
optional-parameter support in KopScript to fall back on, and it sidesteps real JS
`Array.sort`'s own well-known footgun — lexicographic-by-default on non-strings). `Slice`'s
both bounds are likewise always required. `Join` is scoped to primitive element types
(`number[]`/`string[]`/`bool[]`) only — a class/interface element has no way to customize
`toString()` in KopScript, so allowing it there would just produce `"[object Object]"`.

`Map`'s result type is the one genuinely polymorphic piece of the whole language — the
result element type is whatever the callback actually returns, not a fixed signature.
A plain function reference (`xs.Map(SomeFunction)`) already carries a fully-known type, so
that case is exact; an inline expression-bodied lambda (`xs.Map((number x) => ...)`) has
its return type inferred from the body. A block-bodied lambda passed to `Map` is a known
v1 gap — its result type can't be inferred that way yet. `Reduce` is similarly
polymorphic, but simpler to infer: its accumulator type comes directly from the `initial`
argument (`xs.Reduce(reducer, initial)`, matching real JS's own argument order), not from
inspecting the callback body.

### Control flow

```ks
if (x > 0) {
  print("positive");
} else {
  print("non-positive");
}

while (x < 10) {
  x = x + 1;
}

for (number i = 0; i < 10; i = i + 1) {
  print(i);
}

foreach (number item in items) {
  print(item);
}
```

`foreach` requires an explicit element type (there's no `var`/type inference in v1).
`break` and `continue` work inside `while`, `for`, and `foreach` loops.

### Types (v1 scope)

`number`, `string`, `bool`, `void`, `T[]` (arrays), class types, interface types, enum
types, function types (`(T, ...) => R`), nullable types (`T?`, with `null` and
compiler-enforced null-checking — see "Nullable types" below), and a single type
parameter on classes/interfaces (`Box<T>` — see "Generics" above).

### Nullable types — `T?`

```ks
string? maybeName = null;
void Greet(string? name) {
  if (name != null) {
    print("Hello, " + name);   // name is `string` here, not `string?`
  } else {
    print("Hello, stranger");
  }
}
```

`?` is a postfix modifier on any type (`Dog?`, or an array either way — `string?[]` is an
array of nullable strings, `string[]?` is a nullable array of strings; order matters).
It's purely a compile-time distinction — erased at codegen, since JS already has native
`null`. The compiler enforces it in both directions:

- A `T?` can never be used where a `T` is expected — accessing a member or index on one
  is a compile error — **unless** it's been narrowed by an `if (x != null)` /
  `if (x == null) {...} else {...}` check (either operand order, and inside a `&&`/`||`
  short-circuit) on that exact local/parameter. Narrowing doesn't survive past the
  checked block, doesn't follow a `this.Field` path (locals/params only), and doesn't
  understand an early-return guard clause (`if (x == null) { return; }`) — each of those
  would need real control-flow analysis, deliberately not taken on in v1.
- Comparing a value to `null` when its type *can't* be null (i.e. isn't itself a `T?`) is
  a compile error, not a silently-always-false comparison — a real static check, not
  just a style rule.
- `x == null`/`x != null` compile to loose `==`/`!=` (every other comparison stays strict
  `===`/`!==`), so a real JS `undefined` — a missed `Array.Find`, an absent field from
  `JSON.parse` — counts as null too, without needing a second check.

See `LLM.md`'s "Nullable types" section for the exhaustive rules if you're generating
code against this.

### Built-ins

`print(...)` compiles to `console.log(...)`.

## Architecture

```
src/
  lexer.ts       tokenizer: source -> Token[]
  tokens.ts      token kind enum + Token type
  ast.ts         AST node type definitions
  parser.ts      recursive-descent parser: Token[] -> AST (Program)
  diagnostics.ts error/warning collection with line/col + source snippets
  types.ts       type system: Type representations + compatibility rules
  checker.ts     semantic analysis: scopes, symbol table, type checking over the AST
  codegen.ts     AST -> JavaScript source string (readable ES2020 output)
  sourcemap.ts   hand-rolled source-map v3 (VLQ) encoder for `ks build`'s .js.map output
  printer.ts     AST -> canonical KopScript source string (what `ks fmt` runs)
  suggest.ts     closest-match ("did you mean 'X'?") lookups feeding diagnostics' `fix` field
  template_lexer.ts/template_parser.ts/template_compiler.ts
                 `template from "<path>.html";` — markup -> the same AST a hand-written
                 Render() would produce, no separate runtime template engine
  css_scoping.ts CSS tokenizer/rewriter for `styles from "<path>.css";`'s scoped selectors
  modules.ts     multi-file orchestration: resolves the `using` graph, checks
                 modules in dependency order, drives codegen across all of them
  cli.ts         `ks build|run|watch|check|fmt <file>` entry point
examples/        sample .ks programs (examples/modules/ is a multi-file one)
test/            vitest unit and end-to-end tests
```

Pipeline, per file: source -> lexer -> parser (AST) -> checker (validates the AST,
collects diagnostics) -> codegen (emits JS). `modules.ts` sits above this: it parses the
entry file and everything it transitively `using`s, topologically sorts them
(dependencies first), and checks each one with its direct dependencies' exports seeded in
— so a class from another file resolves exactly like a local one once the checker starts.
Codegen then runs per file, turning each `using` into a real `import` statement and each
exported top-level declaration into a real `export`. The CLI aborts before writing
anything if *any* file in the graph has errors, and otherwise writes one `.js` file next
to each `.ks` source and (for `run`) executes the entry file's output with `node`.

Interfaces and the virtual/override discipline are purely compile-time: JS methods are
always dynamically dispatched, so codegen doesn't need to do anything special for either
one — the checker just validates the contract before code is ever emitted. Enums compile
to a small frozen object (`Object.freeze({ Red: 0, Green: 1, ... })`); interfaces have no
runtime representation at all and are dropped from the emitted JS entirely.

## CLI

```
ks build <file.ks>   # type-check and emit <file>.js (+ <file>.js.map) next to the source
ks run <file.ks>     # build, then execute the emitted JS with node --enable-source-maps
ks watch <file.ks>   # build, then rebuild on every change to any file in the graph
ks check <file.ks>   # type-check only — no output files written
ks fmt <file.ks>            # rewrite the file to canonical formatting, in place
ks fmt <file.ks> --check    # exit 1 if it isn't already formatted; write nothing either way
```

`build` and `check` both accept `--json`, which replaces all human-readable output with a
single JSON object on stdout — meant for CI or a tool/agent parsing the result
programmatically instead of scraping formatted text:

```json
{
  "success": false,
  "diagnostics": [
    { "code": "KS4083", "severity": "error", "message": "Class 'Widget' has no member 'Vlaue'", "line": 3, "col": 13, "fix": "Did you mean 'Value'?", "file": "/abs/path/to/file.ks" }
  ],
  "written": []
}
```

Some diagnostics also carry a `fix` field — a real "did you mean 'X'?" against something
actually declared, for the unknown-member/function/identifier/type family of errors, a
missing `await` wherever it's the actual, always-correct explanation for a type mismatch,
or a precise next step for a range of other specific mistakes (an override that doesn't
match its base signature, an unsatisfied generic constraint, a `match` missing its trailing
`_` arm, assigning to a get-only property outside its constructor, a template with more
than one root element, and more). The entire diagnostic surface has been reviewed with the
same bar (`test/diagnostics.test.ts` enforces every code stays unique as new ones ship —
see its own real count, not a number restated by hand here); most still have no `fix` —
that's deliberate, not a gap, since
one is only added where it's genuinely more actionable than the message alone. Where it's
present, it's meant to be actionable directly, by a human or an AI agent, without needing to
re-derive it from the message alone.

`written` lists the absolute paths of every `.js` file actually written (always empty for
`check`, and for `build` on failure — nothing is written unless the whole graph is
error-free). A missing entry file reports `{ "success": false, "diagnostics": [],
"written": [], "error": "cannot find file '...'" }` instead of throwing. Exit code is 0
exactly when `success` is `true`, both with and without `--json`.

Every diagnostic carries a stable `code` (`KS` + a number) alongside its human-readable
`message` — meant for a tool/agent to pattern-match reliably (`code === "KS4065"`) instead
of parsing prose that can be reworded between versions. Codes are grouped by pipeline stage
and never reused once assigned: `KS1xxx` lexer, `KS2xxx` parser, `KS3xxx` module/`using`
resolution, `KS4xxx` the checker (by far the largest category — most real type errors live
here), `KS5xxx` templates. There's still no generated reference doc mapping every code to an
explanation — `message` (plus `fix`, when present — see above) is the explanation; `code` is
for matching reliably across wording changes, not for looking up a separate page.

**Source maps**: `build` writes a real source-map v3 `<file>.js.map` alongside every
`<file>.js`, with the original `.ks` source embedded (`sourcesContent`) so a deployed app
doesn't need to ship its `.ks` files for devtools/stack traces to show real source. `run`
passes Node's own `--enable-source-maps` flag automatically, so an uncaught exception
during `ks run` names the real `.ks` file and line, not the generated `.js`. Hand-rolled
(base64 VLQ), not the `source-map` npm package — kopscript has zero runtime dependencies
and this keeps it that way (see `src/sourcemap.ts`). **Scope**: statement-level, not full
expression/column-level — every statement (a class member, a block statement, a top-level
declaration) gets its own mapping, but a specific sub-expression *within* one line doesn't.
This is a deliberate v1 cut, the same spirit as generics/nullable types/templates: real,
useful debugging (correct stack-trace lines, working breakpoints) without a full rewrite of
codegen's string-concatenation architecture into a position-tracking writer.

**Formatter**: `ks fmt <file.ks>` rewrites a file to canonical formatting in place —
2-space indent, K&R braces, single space around operators, one blank line between
`using`s and the first declaration and between top-level declarations, one blank line
between class members, no trailing commas, double quotes only. `ks fmt <file.ks> --check`
reports (exit 1) whether a file isn't already formatted, without writing anything — for CI,
the same convention `gofmt -l`/`prettier --check` use. Single-file only: formatting needs
only lexing and parsing, not the full `using`-graph resolution or type-checking `build`/
`check`/`watch` do. Comments survive formatting (`src/tokens.ts`'s `CommentTrivia`, attached
to tokens by the lexer and looked up by `src/printer.ts` per node it prints) — two
deliberate v1 choices worth knowing about, not silent surprises:
- **Class members print in a fixed canonical order** (fields, then properties, then the
  constructor, then methods), regardless of how the original source interleaved them —
  `ClassDecl` buckets members into separate arrays (see `src/ast.ts`), so any original
  interleaving between e.g. a field and a method is already gone by the time the AST
  exists; there's nothing to preserve.
- **Every body always prints as a full multi-line block**, even a single-statement one a
  human might have collapsed onto one line — no "when do I collapse this" judgment call for
  the formatter to get subtly wrong.

Verified against every file in `examples/` and Kopular's own real `.ks` sources: formatting
twice is a no-op (idempotent), the reformatted file reparses with zero diagnostics, and —
the real proof formatting never changes what a program means — the *generated JS* for the
original and the reformatted source is byte-for-byte identical.

During development, use `npm run ks -- <build|run|watch|check|fmt> <file.ks>` (backed by
`tsx`), or run `npm run build` to compile the TypeScript compiler itself to `dist/` and
use `node dist/cli.js` directly.

`watch` rebuilds on a save to *any* `.ks` file it reached while compiling — the entry and
everything it (transitively, non-transitively per-file) `using`s — not just the entry
file, plus any `.html` file referenced via `template from "...";` in one of those classes,
and re-establishes its watch list after every rebuild since the dependency set
itself can change (a `using` added or removed, or a `template` declaration's path). This is the piece that makes a
browser-facing dev loop bearable: run `ks watch` in one terminal, a static file server in
another, and refreshing the browser after a save is the only manual step left — there's
no watch-triggered auto-refresh, since KopScript has no dev-server integration to push
that to the page. See [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)
for this workflow in practice.

## Editor support

`editors/vscode/` is a local-install-only VS Code extension providing `.ks` syntax
highlighting (a TextMate grammar — comments, all three string forms including highlighted
interpolation expressions, keywords, types, function calls), bracket/comment
configuration, and real, live diagnostics + type-on-hover — it type-checks the whole
module graph reachable from the active file (via the compiler's own `compileGraph`) and
surfaces the result as squiggles/Problems-panel entries and hover tooltips, cross-file
aware (editing a file another one `using`s re-checks both). No separate language-server
process, no Marketplace listing — see `editors/vscode/README.md`
for installing it locally (`Developer: Install Extension from Location...`, or symlink it
into your extensions folder).

## Status

This is a v1 / hobby-project scope. Nullable types (`T?`) and generics (multiple type
parameters, single-interface constraints, generic inheritance, and free generic functions)
have all shipped — see above for the full "Generics" section. What generics still
deliberately doesn't cover: variance, multiple constraints per parameter, generic methods
(only free functions), and more than one generic entry per base list — each a real,
separable extension rather than a v1 oversight. Also not yet supported: static
auto-properties, interface properties (methods only), and nested functions/classes.

`async`/`await`, `task<T>`, and `try`/`catch`/`finally`/`throw` are now in place (see the
language tour above) — the ceiling that's left is what's *inside* those: no async lambdas,
and no way to build a `task` value by hand outside an `async` function body.

`ks fmt` now exists (see "Formatter" above) — it was blocked on the lexer discarding every
comment entirely, which an AST-based pretty-printer would otherwise silently delete on
reformat, until `Token.leadingComments` (`src/tokens.ts`'s `CommentTrivia`) closed that gap.
`src/printer.ts` looks up each comment by the line number of the token it attached to
(not a `(line, col)` pair — several node kinds, e.g. `FieldDecl`, deliberately record their
position at their *type* token, after any `public`/`static`/etc. modifiers, which a
column-exact lookup would miss).

### A frontend framework: Kopular

The language shape — modules, closures, DOM interop, `state<T>`, and `extern`/`virtual`
subclassing across a package boundary — exists to support real UI components, not just
scripts. That framework itself, [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular),
lives in its own repo, published to npm as `kopular` and consumed like any other package
(no local checkout or `file:` dependency needed). A real app built on both lives in
[KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo) — several
routed pages, `state<T>`-driven reactivity, constructor-injected services via a
composition root (no DI container), real-URL (History API) routing, and Angular-style
markup templates (see [Templates](#templates) above) with `*if`/`*for` structural
directives — verified against a real DOM via jsdom and in an actual browser.
