kopscript 0.4.0 → 0.5.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 -1
- package/README.md +15 -0
- package/dist/checker.js +39 -20
- package/dist/lexer.js +16 -1
- package/dist/parser.js +2 -1
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -201,6 +201,11 @@ $"Hello {name}, you are {age} years old" // interpolated string
|
|
|
201
201
|
r"^[a-z]+$" // regex literal (only meaningful as a match pattern)
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
A regex literal's contents pass to the real `RegExp` unprocessed — `\s`, `\d`, `\w`, `\.`,
|
|
205
|
+
etc. all mean real regex syntax, not string escapes (only `\"` is special, letting a
|
|
206
|
+
literal `"` appear before the closing quote). This differs from every other string-like
|
|
207
|
+
literal in the language, which do interpret `\n`/`\t`/`\\`/etc.
|
|
208
|
+
|
|
204
209
|
### Lambdas
|
|
205
210
|
|
|
206
211
|
```ks
|
|
@@ -276,7 +281,8 @@ Box<string> sb = new Box<string>("hi");
|
|
|
276
281
|
print(nb.Get()); // 5
|
|
277
282
|
```
|
|
278
283
|
|
|
279
|
-
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.
|
|
280
286
|
Erased at codegen (JS has no generics either; `Box<number>` and `Box<string>` both
|
|
281
287
|
compile to the exact same plain `class Box`), so there's no runtime cost and no way to
|
|
282
288
|
inspect `T` at runtime.
|
|
@@ -382,6 +388,12 @@ KopScript-declared name exactly. Extern class members use real JS member names v
|
|
|
382
388
|
(camelCase, no rename mechanism). No inheritance modeling between two `extern class`
|
|
383
389
|
declarations — each stands alone.
|
|
384
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
|
+
|
|
385
397
|
**Never write `async` on an extern function/method signature** — declare its return type
|
|
386
398
|
as `task`/`task<T>` directly (`task<string> text();`, not `async task<string> text();`).
|
|
387
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,
|
|
@@ -856,6 +866,15 @@ export class Checker {
|
|
|
856
866
|
this.checkBlock(decl.body, scope, { returnType, currentClass: null, inConstructor: false, loopDepth: 0, isAsync: decl.isAsync });
|
|
857
867
|
}
|
|
858
868
|
checkClassBody(decl) {
|
|
869
|
+
// Constructor/method *bodies* run in this same scope registerClass used
|
|
870
|
+
// for the *signatures* (see withTypeParamInScope) — without it, `T`
|
|
871
|
+
// resolves everywhere in a generic class's declared field/param/return
|
|
872
|
+
// types but not inside a method body itself (a local `T x = ...;`, or a
|
|
873
|
+
// lambda parameter typed `T`), which would make the type parameter
|
|
874
|
+
// usable only at the class's boundary and not inside its own logic.
|
|
875
|
+
this.withTypeParamInScope(decl.typeParam, () => this.checkClassBodyInner(decl));
|
|
876
|
+
}
|
|
877
|
+
checkClassBodyInner(decl) {
|
|
859
878
|
const info = this.classes.get(decl.name);
|
|
860
879
|
if (decl.constructor) {
|
|
861
880
|
const paramScope = new Scope();
|
package/dist/lexer.js
CHANGED
|
@@ -133,12 +133,27 @@ export class Lexer {
|
|
|
133
133
|
}
|
|
134
134
|
return this.make(TokenKind.InterpolatedString, raw, line, col);
|
|
135
135
|
}
|
|
136
|
+
// Deliberately its own reader, not `readStringChar` — a regex literal's
|
|
137
|
+
// backslash escapes (`\s`, `\d`, `\.`, ...) are regex syntax, meant to
|
|
138
|
+
// reach the real `RegExp` constructor unchanged, not string escapes to be
|
|
139
|
+
// interpreted here. `readStringChar` turns any unrecognized `\x` into a
|
|
140
|
+
// bare `x` (dropping the backslash), which silently corrupts almost every
|
|
141
|
+
// realistic pattern. Only `\"` gets special handling, so a literal quote
|
|
142
|
+
// can appear inside a pattern without ending the literal early; every
|
|
143
|
+
// other `\` + character (including `\\` itself) passes through verbatim.
|
|
136
144
|
readRegexLiteral(line, col) {
|
|
137
145
|
this.advance(); // 'r'
|
|
138
146
|
this.advance(); // opening quote
|
|
139
147
|
let value = "";
|
|
140
148
|
while (!this.isAtEnd() && this.peek() !== '"') {
|
|
141
|
-
|
|
149
|
+
const c = this.advance();
|
|
150
|
+
if (c === "\\" && !this.isAtEnd()) {
|
|
151
|
+
const next = this.advance();
|
|
152
|
+
value += next === '"' ? '"' : "\\" + next;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
value += c;
|
|
156
|
+
}
|
|
142
157
|
}
|
|
143
158
|
if (this.isAtEnd()) {
|
|
144
159
|
this.diagnostics.error("Unterminated regex literal", line, col);
|
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