kopscript 0.4.1 → 0.5.1
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 +8 -1
- package/README.md +15 -0
- package/dist/checker.js +30 -20
- package/dist/codegen.js +19 -3
- package/dist/parser.js +2 -1
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -281,7 +281,8 @@ Box<string> sb = new Box<string>("hi");
|
|
|
281
281
|
print(nb.Get()); // 5
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
-
One unconstrained, invariant type parameter per class/interface
|
|
284
|
+
One unconstrained, invariant type parameter per class/interface (real *or* `extern` — see
|
|
285
|
+
the `extern` section below) — the whole feature.
|
|
285
286
|
Erased at codegen (JS has no generics either; `Box<number>` and `Box<string>` both
|
|
286
287
|
compile to the exact same plain `class Box`), so there's no runtime cost and no way to
|
|
287
288
|
inspect `T` at runtime.
|
|
@@ -387,6 +388,12 @@ KopScript-declared name exactly. Extern class members use real JS member names v
|
|
|
387
388
|
(camelCase, no rename mechanism). No inheritance modeling between two `extern class`
|
|
388
389
|
declarations — each stands alone.
|
|
389
390
|
|
|
391
|
+
`extern class` can carry `<T>` (`extern class Box<T> { constructor(T v); T Value { get; } }
|
|
392
|
+
from "some-package";`) — identical rules to a real generic class (see "Generics" above:
|
|
393
|
+
one unconstrained invariant parameter, erased, no base-list generics), so a generic type
|
|
394
|
+
from another package instantiates and type-checks exactly like a local one
|
|
395
|
+
(`Box<number>`, arity/invariance errors included).
|
|
396
|
+
|
|
390
397
|
**Never write `async` on an extern function/method signature** — declare its return type
|
|
391
398
|
as `task`/`task<T>` directly (`task<string> text();`, not `async task<string> text();`).
|
|
392
399
|
`async` only means something for a *body* the checker validates (legalizing `await`
|
package/README.md
CHANGED
|
@@ -347,6 +347,21 @@ consumes `Component`/`Router` themselves via `extern class ... from "kopular/...
|
|
|
347
347
|
proving `extern` works as a real cross-*package* boundary, not just for describing DOM
|
|
348
348
|
globals within a single project.
|
|
349
349
|
|
|
350
|
+
An `extern class` can carry its own type parameter, `extern class Box<T> { ... }` —
|
|
351
|
+
exactly the same single-type-parameter rules as a real generic class (see "Generics"
|
|
352
|
+
above: invariant, unconstrained, erased, can't appear in a base list), so a generic type
|
|
353
|
+
from another package (e.g. Kopular's `FormField<T>`) can be described and instantiated
|
|
354
|
+
generically, not just per concrete type:
|
|
355
|
+
|
|
356
|
+
```ks
|
|
357
|
+
extern class Box<T> {
|
|
358
|
+
constructor(T v);
|
|
359
|
+
T Value { get; }
|
|
360
|
+
} from "some-package";
|
|
361
|
+
|
|
362
|
+
Box<number> nb = new Box<number>(5);
|
|
363
|
+
```
|
|
364
|
+
|
|
350
365
|
Now that KopScript has `async`/`await` and `task<T>` (see below), a Promise-based JS API is
|
|
351
366
|
describable too — `extern task<string> Fetch(...) from "..." as "fetch";` is legitimate,
|
|
352
367
|
and `await`ing it works exactly like awaiting any other KopScript task. What's still not cleanly
|
package/dist/checker.js
CHANGED
|
@@ -114,6 +114,9 @@ export class Checker {
|
|
|
114
114
|
for (const i of interfaceDecls)
|
|
115
115
|
if (i.typeParam)
|
|
116
116
|
this.genericTypeParams.set(i.name, i.typeParam);
|
|
117
|
+
for (const c of externClassDecls)
|
|
118
|
+
if (c.typeParam)
|
|
119
|
+
this.genericTypeParams.set(c.name, c.typeParam);
|
|
117
120
|
for (const e of enumDecls)
|
|
118
121
|
this.registerEnum(e);
|
|
119
122
|
for (const i of interfaceDecls)
|
|
@@ -169,28 +172,35 @@ export class Checker {
|
|
|
169
172
|
});
|
|
170
173
|
}
|
|
171
174
|
registerExternClass(decl) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
175
|
+
// Same withTypeParamInScope treatment registerClass gives a real class's
|
|
176
|
+
// signature (fields/methods/ctor params) — an extern class's own `T`
|
|
177
|
+
// needs to resolve the same way while its declared members are being
|
|
178
|
+
// resolved, e.g. `state<T> Value;` in an `extern class FormField<T>`.
|
|
179
|
+
const { fields, staticFields, methods, staticMethods, ownCtorParams } = this.withTypeParamInScope(decl.typeParam, () => {
|
|
180
|
+
const fields = new Map();
|
|
181
|
+
const staticFields = new Map();
|
|
182
|
+
for (const p of decl.properties) {
|
|
183
|
+
const info = { type: this.resolveType(p.type, decl.line, decl.col), visibility: "public", hasSetter: p.hasSetter };
|
|
184
|
+
(p.isStatic ? staticFields : fields).set(p.name, info);
|
|
185
|
+
}
|
|
186
|
+
const methods = new Map();
|
|
187
|
+
const staticMethods = new Map();
|
|
188
|
+
for (const m of decl.methods) {
|
|
189
|
+
const info = {
|
|
190
|
+
params: m.params.map((p) => this.resolveType(p.type, decl.line, decl.col)),
|
|
191
|
+
returnType: this.resolveType(m.returnType, decl.line, decl.col),
|
|
192
|
+
visibility: "public",
|
|
193
|
+
isVirtual: m.isVirtual,
|
|
194
|
+
isOverride: false,
|
|
195
|
+
};
|
|
196
|
+
(m.isStatic ? staticMethods : methods).set(m.name, info);
|
|
197
|
+
}
|
|
198
|
+
const ownCtorParams = decl.hasConstructor ? decl.ctorParams.map((p) => this.resolveType(p.type, decl.line, decl.col)) : null;
|
|
199
|
+
return { fields, staticFields, methods, staticMethods, ownCtorParams };
|
|
200
|
+
});
|
|
191
201
|
this.classes.set(decl.name, {
|
|
192
202
|
name: decl.name,
|
|
193
|
-
typeParam:
|
|
203
|
+
typeParam: decl.typeParam,
|
|
194
204
|
superclass: null,
|
|
195
205
|
interfaces: [],
|
|
196
206
|
fields,
|
package/dist/codegen.js
CHANGED
|
@@ -147,10 +147,10 @@ export class CodeGenerator {
|
|
|
147
147
|
const init = stmt.init
|
|
148
148
|
? stmt.init.kind === "VarDecl"
|
|
149
149
|
? `${stmt.init.isConst ? "const" : "let"} ${stmt.init.name} = ${this.genExpr(stmt.init.init)}`
|
|
150
|
-
: this.
|
|
150
|
+
: this.genExprAtStatementLevel(stmt.init.expression)
|
|
151
151
|
: "";
|
|
152
152
|
const cond = stmt.condition ? this.genExpr(stmt.condition) : "";
|
|
153
|
-
const update = stmt.update ? this.
|
|
153
|
+
const update = stmt.update ? this.genExprAtStatementLevel(stmt.update) : "";
|
|
154
154
|
return `${pad}for (${init}; ${cond}; ${update}) ${this.genBlock(stmt.body, indent).trimStart()}`;
|
|
155
155
|
}
|
|
156
156
|
case "ForInStatement":
|
|
@@ -162,7 +162,7 @@ export class CodeGenerator {
|
|
|
162
162
|
case "ContinueStatement":
|
|
163
163
|
return `${pad}continue;`;
|
|
164
164
|
case "ExpressionStatement":
|
|
165
|
-
return `${pad}${this.
|
|
165
|
+
return `${pad}${this.genExprAtStatementLevel(stmt.expression)};`;
|
|
166
166
|
case "TryStatement":
|
|
167
167
|
return this.genTry(stmt, indent);
|
|
168
168
|
case "ThrowStatement":
|
|
@@ -245,6 +245,22 @@ export class CodeGenerator {
|
|
|
245
245
|
return `${pad}constructor(${params}) ${bodyStr}`;
|
|
246
246
|
}
|
|
247
247
|
// ---------- expressions ----------
|
|
248
|
+
// genExpr wraps an assignment in parens unconditionally (`(x = y)`), since
|
|
249
|
+
// it has to stay safely embeddable wherever an expression can appear —
|
|
250
|
+
// inside a binary expression, a call argument, anywhere precedence could
|
|
251
|
+
// matter. At true statement level (an expression-statement, or a for
|
|
252
|
+
// loop's own init/update clause) nothing surrounds it that parens could
|
|
253
|
+
// ever matter for, so they're pure noise there — `(this.Count = 0);`
|
|
254
|
+
// reads like defensive generated code, not something a person would
|
|
255
|
+
// write by hand. Used at exactly those three call sites, not inside
|
|
256
|
+
// genExpr itself, since every other embedding context still needs the
|
|
257
|
+
// defensive parens.
|
|
258
|
+
genExprAtStatementLevel(expr) {
|
|
259
|
+
if (expr.kind === "AssignExpr") {
|
|
260
|
+
return `${this.genExpr(expr.target)} = ${this.genExpr(expr.value)}`;
|
|
261
|
+
}
|
|
262
|
+
return this.genExpr(expr);
|
|
263
|
+
}
|
|
248
264
|
genExpr(expr) {
|
|
249
265
|
switch (expr.kind) {
|
|
250
266
|
case "NumberLiteral":
|
package/dist/parser.js
CHANGED
|
@@ -455,6 +455,7 @@ export class Parser {
|
|
|
455
455
|
}
|
|
456
456
|
parseExternClassBody(start, isExported) {
|
|
457
457
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
458
|
+
const typeParam = this.parseOptionalTypeParam();
|
|
458
459
|
this.consume(TokenKind.LBrace, "Expected '{' before extern class body");
|
|
459
460
|
let hasConstructor = false;
|
|
460
461
|
let ctorParams = [];
|
|
@@ -519,7 +520,7 @@ export class Parser {
|
|
|
519
520
|
this.consume(TokenKind.RBrace, "Expected '}' after extern class body");
|
|
520
521
|
const { modulePath, jsName } = this.parseExternTail(name);
|
|
521
522
|
this.consume(TokenKind.Semicolon, "Expected ';' after extern class declaration");
|
|
522
|
-
return { kind: "ExternClassDecl", isExported, name, jsName, hasConstructor, ctorParams, properties, methods, modulePath, line: start.line, col: start.col };
|
|
523
|
+
return { kind: "ExternClassDecl", isExported, name, typeParam, jsName, hasConstructor, ctorParams, properties, methods, modulePath, line: start.line, col: start.col };
|
|
523
524
|
}
|
|
524
525
|
parseBlock() {
|
|
525
526
|
const start = this.consume(TokenKind.LBrace, "Expected '{'");
|
package/package.json
CHANGED