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 +13 -8
- package/README.md +20 -17
- package/dist/template_compiler.js +37 -9
- package/package.json +2 -2
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 —
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
(`
|
|
560
|
-
Kopular's `
|
|
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
|
|
542
|
-
|
|
543
|
-
button.
|
|
544
|
-
button.
|
|
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
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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.
|
|
605
|
-
| `(event)="stmt"` | `el.
|
|
606
|
-
| `[prop]="expr"` | `el.
|
|
607
|
-
| `class="..."` (static) | `el.
|
|
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
|
-
`
|
|
627
|
-
exactly the
|
|
628
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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("
|
|
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(
|
|
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(
|
|
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(
|
|
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, "
|
|
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), "
|
|
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.
|
|
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/
|
|
10
|
+
"url": "https://dev.azure.com/koppinator/Koppindependence/_git/KopScript"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://kopular.dev",
|
|
13
13
|
"keywords": [
|