kopscript 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LLM.md CHANGED
@@ -540,11 +540,16 @@ class Counter : Component {
540
540
  - One `template` per class; a class with both `template` and a hand-written `Render()` is a
541
541
  compile error.
542
542
  - Desugars, between parsing and checking, into the exact same `MethodDecl` AST a
543
- hand-written `Render()` would produce — no runtime template engine, no virtual DOM.
544
- `{{ }}`/binding contents are real KopScript, parsed and type-checked normally.
545
- - Bindings: `{{ expr }}` (text interpolation, `InterpolatedStringLiteral`), `(event)="stmt"`
546
- (→ `addEventListener`), `[prop]="expr"` (→ plain assignment, checked like any member
547
- assignment), static `attr="..."` (`class` aliases to `className`).
543
+ hand-written `Render()` would produce — building a `VElement` tree as data, no runtime
544
+ template engine, no diffing of its own (Kopular's own `Component` base class does that
545
+ against the tree this produces). `{{ }}`/binding contents are real KopScript, parsed and
546
+ type-checked normally.
547
+ - Bindings: `{{ expr }}` (text interpolation, `VElement.TextContent = InterpolatedStringLiteral`),
548
+ `(event)="stmt"` (→ assignment to one of `VElement`'s four named event fields —
549
+ `OnClick`/`OnInput`/`OnBlur`/`OnChange`; any other event name is a compile error),
550
+ `[prop]="expr"` (→ a plain field assignment for `id`/`className`/`value`, or
551
+ `VElement.SetAttr("prop", expr)` for anything else), static `attr="..."` (`class` aliases
552
+ to `className`).
548
553
  - Structural directives: `*if="expr"` (→ real `if`), `*for="Type varName of expr"` (→ real
549
554
  `for..in`; the element type is explicit — no inference, same stance as Generics).
550
555
  At most one structural directive per element.
@@ -556,9 +561,9 @@ class Counter : Component {
556
561
  element children under one element. No two-way binding, no pipes, no stacked directives.
557
562
  - `ks watch` tracks the referenced `.html` file as well as `.ks` dependencies.
558
563
  - Layering note: `template from` is kopscript grammar, but what it desugars *to*
559
- (`document.createElement`/`.appendChild`/`.textContent`/`.addEventListener`) assumes
560
- Kopular's `dom.ks` DOM surface specifically — a deliberate, documented coupling, not a
561
- generic pluggable target.
564
+ (`VElement.Create`/`.AppendChild`/`.TextContent`/`.SetAttr`/the named `On*` event fields)
565
+ assumes Kopular's `velement.ks` surface specifically — a deliberate, documented coupling,
566
+ not a generic pluggable target.
562
567
 
563
568
  ## Keywords (reserved, lowercase, exact match)
564
569
 
package/README.md CHANGED
@@ -538,12 +538,12 @@ class Counter : Component {
538
538
  this.Count.Subscribe((number v) => this.Update());
539
539
  }
540
540
 
541
- public override Element Render() {
542
- Element button = document.createElement("button");
543
- button.textContent = "Count: " + this.Count.Value;
544
- button.addEventListener("click", (Event e) => {
541
+ public override VElement Render() {
542
+ VElement button = VElement.Create("button");
543
+ button.TextContent = "Count: " + this.Count.Value;
544
+ button.OnClick = (Event e) => {
545
545
  this.Count.Value = this.Count.Value + 1; // Update() fires automatically
546
- });
546
+ };
547
547
  return button;
548
548
  }
549
549
  }
@@ -587,11 +587,13 @@ class Counter : Component {
587
587
  This compiles to exactly the `Render()` method you'd otherwise write by hand — the
588
588
  template compiler is a pass that runs between parsing and type-checking, turning the
589
589
  markup into ordinary `MethodDecl`/statement/expression AST nodes and splicing the result
590
- into the class before checking ever runs. There's no separate runtime template engine, no
591
- virtual DOM diffing, and no interpreted expression language: `{{ Count.Value }}` and
592
- `(click)="Increment()"` contain real KopScript, parsed and type-checked exactly like
593
- anything else in the file, with errors reported at their real position in the `.html`
594
- file, not the `.ks` file.
590
+ into the class before checking ever runs. There's no separate runtime template engine and
591
+ no interpreted expression language: `{{ Count.Value }}` and `(click)="Increment()"`
592
+ contain real KopScript, parsed and type-checked exactly like anything else in the file,
593
+ with errors reported at their real position in the `.html` file, not the `.ks` file. The
594
+ compiler itself does no diffing — it just builds a `VElement` tree as data; Kopular's own
595
+ `Component` base class is what diffs that tree against the previous render and patches
596
+ real DOM (see Kopular's own README/CHANGELOG for that engine).
595
597
 
596
598
  A class may have a `template` or a hand-written `Render()`, never both — that's a compile
597
599
  error. `ks watch` also tracks the referenced `.html` file, so editing markup alone
@@ -601,10 +603,10 @@ Supported bindings and directives:
601
603
 
602
604
  | Syntax | Desugars to |
603
605
  | --------------------------- | --------------------------------------------------------- |
604
- | `{{ expr }}` (in text) | `el.textContent = $"...{expr}...";` (an `InterpolatedStringLiteral`, same as `$"..."`) |
605
- | `(event)="stmt"` | `el.addEventListener("event", (Event e) => { stmt });` |
606
- | `[prop]="expr"` | `el.prop = expr;` a plain assignment, checked like any other |
607
- | `class="..."` (static) | `el.className = "...";` (aliased, since `class` is a KopScript keyword) |
606
+ | `{{ expr }}` (in text) | `el.TextContent = $"...{expr}...";` (an `InterpolatedStringLiteral`, same as `$"..."`) |
607
+ | `(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 |
608
+ | `[prop]="expr"` | `el.Prop = expr;` for `id`/`className`/`value` (`VElement`'s own named fields); `el.SetAttr("prop", expr);` for anything else |
609
+ | `class="..."` (static) | `el.ClassName = "...";` (aliased, since `class` is a KopScript keyword) |
608
610
  | `*if="expr"` | a real `if (expr) { ... }` around the element's creation |
609
611
  | `*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) |
610
612
 
@@ -623,9 +625,10 @@ two-way binding, no pipes, no stacking two structural directives on one element.
623
625
 
624
626
  **A deliberate layering note**: the `template from` syntax lives in kopscript's own
625
627
  grammar (Kopular can't extend a language it doesn't own), but what it desugars *to* —
626
- `document.createElement`, `.appendChild`, `.textContent`, `.addEventListener` — assumes
627
- exactly the DOM surface [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)'s
628
- `dom.ks` declares. That's a real coupling from the compiler to one specific consumer,
628
+ `VElement.Create`, `.AppendChild`, `.TextContent`, `.SetAttr`, the named `On*` event
629
+ fields — assumes exactly the surface
630
+ [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)'s
631
+ `velement.ks` declares. That's a real coupling from the compiler to one specific consumer,
629
632
  accepted deliberately rather than building a generic pluggable desugaring-target system
630
633
  for a hypothetical second framework that doesn't exist today. If one ever does, that's the
631
634
  point to generalize this.
@@ -1,6 +1,16 @@
1
- const ELEMENT_TYPE = { kind: "NamedType", name: "Element", typeArgs: null, line: 0, col: 0 };
1
+ const VELEMENT_TYPE = { kind: "NamedType", name: "VElement", typeArgs: null, line: 0, col: 0 };
2
2
  const VOID_TYPE = { kind: "NamedType", name: "void", typeArgs: null, line: 0, col: 0 };
3
3
  const EVENT_TYPE = { kind: "NamedType", name: "Event", typeArgs: null, line: 0, col: 0 };
4
+ // The only attrs/prop bindings with a real named VElement field — anything
5
+ // else goes through SetAttr instead (see buildAttrAssignment). Keys are
6
+ // post-alias names (see template_parser.ts's PROPERTY_ALIASES — "class" is
7
+ // already "className" by the time it reaches here).
8
+ const KNOWN_FIELD_NAMES = { id: "Id", className: "ClassName", value: "Value" };
9
+ // VElement's own fixed, named event slots (see velement.ks) — there's no
10
+ // generic addEventListener on VElement (event handlers are part of the
11
+ // VNode's own data, not wired against a live DOM node), so a template can
12
+ // only bind one of these four.
13
+ const KNOWN_EVENT_NAMES = { click: "OnClick", input: "OnInput", blur: "OnBlur", change: "OnChange" };
4
14
  function ident(name, at) {
5
15
  return { kind: "Identifier", name, line: at.line, col: at.col };
6
16
  }
@@ -20,7 +30,7 @@ function assign(target, value, at) {
20
30
  return exprStatement({ kind: "AssignExpr", target, value, line: at.line, col: at.col }, at);
21
31
  }
22
32
  function varDecl(name, init, at) {
23
- return { kind: "VarDecl", isConst: false, name, nameLine: at.line, nameCol: at.col, type: ELEMENT_TYPE, init, line: at.line, col: at.col };
33
+ return { kind: "VarDecl", isConst: false, name, nameLine: at.line, nameCol: at.col, type: VELEMENT_TYPE, init, line: at.line, col: at.col };
24
34
  }
25
35
  function block(statements, at) {
26
36
  return { kind: "Block", statements, line: at.line, col: at.col };
@@ -181,7 +191,7 @@ export class TemplateCompiler {
181
191
  nameLine: templateDeclAt.line,
182
192
  nameCol: templateDeclAt.col,
183
193
  params: [],
184
- returnType: ELEMENT_TYPE,
194
+ returnType: VELEMENT_TYPE,
185
195
  body: block(statements, templateDeclAt),
186
196
  line: templateDeclAt.line,
187
197
  col: templateDeclAt.col,
@@ -200,14 +210,19 @@ export class TemplateCompiler {
200
210
  const varName = this.freshVar();
201
211
  const statements = [];
202
212
  const self = ident(varName, at);
203
- statements.push(varDecl(varName, call(member(ident("document", at), "createElement", at), [stringLiteral(node.tag, at)], at), at));
213
+ statements.push(varDecl(varName, call(member(ident("VElement", at), "Create", at), [stringLiteral(node.tag, at)], at), at));
204
214
  for (const attr of node.staticAttrs)
205
- statements.push(assign(member(self, attr.name, attr), stringLiteral(attr.value, attr), attr));
215
+ statements.push(this.buildAttrAssignment(self, attr.name, stringLiteral(attr.value, attr), attr));
206
216
  for (const bind of node.propBindings) {
207
217
  const value = this.resolve(bind.value, localScope);
208
- statements.push(assign(member(self, bind.name, bind), value, bind));
218
+ statements.push(this.buildAttrAssignment(self, bind.name, value, bind));
209
219
  }
210
220
  for (const bind of node.eventBindings) {
221
+ const fieldName = KNOWN_EVENT_NAMES[bind.name];
222
+ if (!fieldName) {
223
+ this.diagnostics.error("KS5016", `Unsupported event binding '(${bind.name})' — a template can only bind (click), (input), (blur), or (change), the same fixed set of named event fields VElement itself has`, bind.line, bind.col);
224
+ continue;
225
+ }
211
226
  const handler = this.resolve(bind.handler, localScope);
212
227
  const handlerParam = { name: "e", type: EVENT_TYPE };
213
228
  const lambda = {
@@ -217,7 +232,7 @@ export class TemplateCompiler {
217
232
  line: bind.line,
218
233
  col: bind.col,
219
234
  };
220
- statements.push(exprStatement(call(member(self, "addEventListener", bind), [stringLiteral(bind.name, bind), lambda], bind), bind));
235
+ statements.push(assign(member(self, fieldName, bind), lambda, bind));
221
236
  }
222
237
  const elementChildren = node.children.filter((c) => c.kind === "element");
223
238
  const textChildren = node.children.filter((c) => c.kind === "text");
@@ -227,7 +242,7 @@ export class TemplateCompiler {
227
242
  else if (textChildren.length > 0) {
228
243
  const parts = textChildren.flatMap((t) => t.parts).map((p) => (p.kind === "Expr" ? { kind: "Expr", expression: this.resolve(p.expression, localScope) } : p));
229
244
  const interpolated = { kind: "InterpolatedStringLiteral", parts, line: at.line, col: at.col };
230
- statements.push(assign(member(self, "textContent", at), interpolated, at));
245
+ statements.push(assign(member(self, "TextContent", at), interpolated, at));
231
246
  }
232
247
  else {
233
248
  for (const child of elementChildren)
@@ -271,6 +286,19 @@ export class TemplateCompiler {
271
286
  return [...statements, this.appendChild(parentVar, elVar, node)];
272
287
  }
273
288
  appendChild(parentVar, childVar, at) {
274
- return exprStatement(call(member(ident(parentVar, at), "appendChild", at), [ident(childVar, at)], at), at);
289
+ return exprStatement(call(member(ident(parentVar, at), "AppendChild", at), [ident(childVar, at)], at), at);
290
+ }
291
+ // A known name (post-alias — "class" already reads "className" by here)
292
+ // becomes a direct assignment to VElement's own named field; anything
293
+ // else — href, src, alt, placeholder, ... — goes through VElement.SetAttr
294
+ // instead, under its original, real HTML attribute name (SetAttr's own
295
+ // name argument is exactly what gets passed to a real setAttribute call
296
+ // at patch time — see vdom.ks).
297
+ buildAttrAssignment(self, name, value, at) {
298
+ const fieldName = KNOWN_FIELD_NAMES[name];
299
+ if (fieldName) {
300
+ return assign(member(self, fieldName, at), value, at);
301
+ }
302
+ return exprStatement(call(member(self, "SetAttr", at), [stringLiteral(name, at), value], at), at);
275
303
  }
276
304
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "kopscript",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "KopScript: a small OOP, strongly-typed language that transpiles to JavaScript, with generics and nullable types",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Joe Koppin <koppinjo@gmail.com>",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://dev.azure.com/koppinator/Koppindependence/_git/Kop"
10
+ "url": "https://dev.azure.com/koppinator/Koppindependence/_git/KopScript"
11
11
  },
12
12
  "homepage": "https://kopular.dev",
13
13
  "keywords": [