kopscript 0.11.1 → 0.13.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 +63 -34
- package/README.md +55 -31
- package/dist/checker.js +325 -161
- package/dist/codegen.js +1 -1
- package/dist/parser.js +51 -24
- package/dist/printer.js +8 -5
- package/dist/types.js +29 -24
- package/package.json +1 -1
package/dist/codegen.js
CHANGED
|
@@ -286,7 +286,7 @@ export class CodeGenerator {
|
|
|
286
286
|
const pad = this.indentStr(indent);
|
|
287
287
|
// The base list mixes an optional superclass with interface names (checker-validated);
|
|
288
288
|
// only the non-interface entry, if any, becomes a JS `extends` clause.
|
|
289
|
-
const superclass = decl.baseList.find((
|
|
289
|
+
const superclass = decl.baseList.find((b) => !this.interfaceNames.has(b.name))?.name ?? null;
|
|
290
290
|
const header = superclass ? `class ${decl.name} extends ${superclass} {` : `class ${decl.name} {`;
|
|
291
291
|
const memberPad = this.indentStr(indent + 1);
|
|
292
292
|
const memberCol = memberPad.length;
|
package/dist/parser.js
CHANGED
|
@@ -243,24 +243,44 @@ export class Parser {
|
|
|
243
243
|
this.consume(TokenKind.RParen, "Expected ')' after parameters");
|
|
244
244
|
return params;
|
|
245
245
|
}
|
|
246
|
-
// `<
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
246
|
+
// `<K, V>` right after a class/interface name — one or more type
|
|
247
|
+
// parameters, comma-separated. Empty array if absent, the overwhelmingly
|
|
248
|
+
// common case. Constraint syntax (`<T : IFoo>`) isn't parsed yet — every
|
|
249
|
+
// entry's `constraint` is always null for now (see AST.TypeParamDecl).
|
|
250
|
+
parseTypeParamList() {
|
|
250
251
|
if (!this.match(TokenKind.Lt))
|
|
251
|
-
return
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
return [];
|
|
253
|
+
const params = [];
|
|
254
|
+
do {
|
|
255
|
+
const nameTok = this.consume(TokenKind.Identifier, "Expected a type parameter name");
|
|
256
|
+
params.push({ name: nameTok.lexeme, constraint: null, line: nameTok.line, col: nameTok.col });
|
|
257
|
+
} while (this.match(TokenKind.Comma));
|
|
258
|
+
this.consume(TokenKind.Gt, "Expected '>' after type parameter list");
|
|
259
|
+
return params;
|
|
260
|
+
}
|
|
261
|
+
// `Bar<T>` or `IBaz` — one base-list entry, with or without its own type
|
|
262
|
+
// arguments. Used by both class and interface base lists.
|
|
263
|
+
parseBaseListEntry() {
|
|
264
|
+
const name = this.consume(TokenKind.Identifier, "Expected base class or interface name").lexeme;
|
|
265
|
+
let typeArgs = null;
|
|
266
|
+
if (this.check(TokenKind.Lt)) {
|
|
267
|
+
this.advance();
|
|
268
|
+
typeArgs = [this.parseType()];
|
|
269
|
+
while (this.match(TokenKind.Comma)) {
|
|
270
|
+
typeArgs.push(this.parseType());
|
|
271
|
+
}
|
|
272
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
273
|
+
}
|
|
274
|
+
return { name, typeArgs };
|
|
255
275
|
}
|
|
256
276
|
parseClassDecl(isExported) {
|
|
257
277
|
const start = this.advance(); // 'class'
|
|
258
278
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
259
|
-
const
|
|
279
|
+
const typeParams = this.parseTypeParamList();
|
|
260
280
|
const baseList = [];
|
|
261
281
|
if (this.match(TokenKind.Colon)) {
|
|
262
282
|
do {
|
|
263
|
-
baseList.push(this.
|
|
283
|
+
baseList.push(this.parseBaseListEntry());
|
|
264
284
|
} while (this.match(TokenKind.Comma));
|
|
265
285
|
}
|
|
266
286
|
this.consume(TokenKind.LBrace, "Expected '{' before class body");
|
|
@@ -412,16 +432,16 @@ export class Parser {
|
|
|
412
432
|
}
|
|
413
433
|
}
|
|
414
434
|
this.consume(TokenKind.RBrace, "Expected '}' after class body");
|
|
415
|
-
return { kind: "ClassDecl", isExported, name,
|
|
435
|
+
return { kind: "ClassDecl", isExported, name, typeParams, baseList, fields, properties, constructor: ctor, methods, template, line: start.line, col: start.col };
|
|
416
436
|
}
|
|
417
437
|
parseInterfaceDecl(isExported) {
|
|
418
438
|
const start = this.advance(); // 'interface'
|
|
419
439
|
const name = this.consume(TokenKind.Identifier, "Expected interface name").lexeme;
|
|
420
|
-
const
|
|
440
|
+
const typeParams = this.parseTypeParamList();
|
|
421
441
|
const baseList = [];
|
|
422
442
|
if (this.match(TokenKind.Colon)) {
|
|
423
443
|
do {
|
|
424
|
-
baseList.push(this.
|
|
444
|
+
baseList.push(this.parseBaseListEntry());
|
|
425
445
|
} while (this.match(TokenKind.Comma));
|
|
426
446
|
}
|
|
427
447
|
this.consume(TokenKind.LBrace, "Expected '{' before interface body");
|
|
@@ -443,7 +463,7 @@ export class Parser {
|
|
|
443
463
|
});
|
|
444
464
|
}
|
|
445
465
|
this.consume(TokenKind.RBrace, "Expected '}' after interface body");
|
|
446
|
-
return { kind: "InterfaceDecl", isExported, name,
|
|
466
|
+
return { kind: "InterfaceDecl", isExported, name, typeParams, baseList, methods, line: start.line, col: start.col };
|
|
447
467
|
}
|
|
448
468
|
parseEnumDecl(isExported) {
|
|
449
469
|
const start = this.advance(); // 'enum'
|
|
@@ -510,7 +530,7 @@ export class Parser {
|
|
|
510
530
|
}
|
|
511
531
|
parseExternClassBody(start, isExported) {
|
|
512
532
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
513
|
-
const
|
|
533
|
+
const typeParams = this.parseTypeParamList();
|
|
514
534
|
this.consume(TokenKind.LBrace, "Expected '{' before extern class body");
|
|
515
535
|
let hasConstructor = false;
|
|
516
536
|
let ctorParams = [];
|
|
@@ -575,7 +595,7 @@ export class Parser {
|
|
|
575
595
|
this.consume(TokenKind.RBrace, "Expected '}' after extern class body");
|
|
576
596
|
const { modulePath, jsName } = this.parseExternTail(name);
|
|
577
597
|
this.consume(TokenKind.Semicolon, "Expected ';' after extern class declaration");
|
|
578
|
-
return { kind: "ExternClassDecl", isExported, name,
|
|
598
|
+
return { kind: "ExternClassDecl", isExported, name, typeParams, jsName, hasConstructor, ctorParams, properties, methods, modulePath, line: start.line, col: start.col };
|
|
579
599
|
}
|
|
580
600
|
parseBlock() {
|
|
581
601
|
const start = this.consume(TokenKind.LBrace, "Expected '{'");
|
|
@@ -705,15 +725,18 @@ export class Parser {
|
|
|
705
725
|
}
|
|
706
726
|
else {
|
|
707
727
|
const nameToken = this.check(TokenKind.Void) ? this.advance() : this.consume(TokenKind.Identifier, "Expected type name");
|
|
708
|
-
// `
|
|
709
|
-
//
|
|
710
|
-
//
|
|
728
|
+
// `Pair<number, string>` — a generic type reference, comma-separated
|
|
729
|
+
// type arguments. Whether `name` actually refers to a declared
|
|
730
|
+
// generic type (and whether the count matches its arity) is a
|
|
711
731
|
// semantic question the checker answers, not the parser.
|
|
712
732
|
let typeArgs = null;
|
|
713
733
|
if (this.check(TokenKind.Lt)) {
|
|
714
734
|
this.advance();
|
|
715
735
|
typeArgs = [this.parseType()];
|
|
716
|
-
this.
|
|
736
|
+
while (this.match(TokenKind.Comma)) {
|
|
737
|
+
typeArgs.push(this.parseType());
|
|
738
|
+
}
|
|
739
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
717
740
|
}
|
|
718
741
|
type = { kind: "NamedType", name: nameToken.lexeme, typeArgs, line: nameToken.line, col: nameToken.col };
|
|
719
742
|
}
|
|
@@ -893,14 +916,18 @@ export class Parser {
|
|
|
893
916
|
if (this.check(TokenKind.New)) {
|
|
894
917
|
this.advance();
|
|
895
918
|
const className = this.consume(TokenKind.Identifier, "Expected class name after 'new'").lexeme;
|
|
896
|
-
// `new
|
|
897
|
-
// always followed by `(`, generic type args or not,
|
|
898
|
-
// instead can only mean a type argument list, never a
|
|
919
|
+
// `new Pair<number, string>(...)` — unambiguous here: `new
|
|
920
|
+
// <Identifier>` is always followed by `(`, generic type args or not,
|
|
921
|
+
// so seeing `<` instead can only mean a type argument list, never a
|
|
922
|
+
// comparison.
|
|
899
923
|
let typeArgs = null;
|
|
900
924
|
if (this.check(TokenKind.Lt)) {
|
|
901
925
|
this.advance();
|
|
902
926
|
typeArgs = [this.parseType()];
|
|
903
|
-
this.
|
|
927
|
+
while (this.match(TokenKind.Comma)) {
|
|
928
|
+
typeArgs.push(this.parseType());
|
|
929
|
+
}
|
|
930
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
904
931
|
}
|
|
905
932
|
this.consume(TokenKind.LParen, "Expected '(' after class name");
|
|
906
933
|
const args = [];
|
package/dist/printer.js
CHANGED
|
@@ -118,6 +118,9 @@ export class Printer {
|
|
|
118
118
|
printParams(params) {
|
|
119
119
|
return params.map((p) => `${this.printType(p.type)} ${p.name}`).join(", ");
|
|
120
120
|
}
|
|
121
|
+
printBaseListEntry(entry) {
|
|
122
|
+
return entry.typeArgs ? `${entry.name}<${entry.typeArgs.map((t) => this.printType(t)).join(", ")}>` : entry.name;
|
|
123
|
+
}
|
|
121
124
|
// ---------- top-level declarations ----------
|
|
122
125
|
printStatement(stmt, indent) {
|
|
123
126
|
const pad = indentStr(indent);
|
|
@@ -212,8 +215,8 @@ export class Printer {
|
|
|
212
215
|
printClass(decl, indent) {
|
|
213
216
|
const pad = indentStr(indent);
|
|
214
217
|
const memberPad = indentStr(indent + 1);
|
|
215
|
-
const nameWithTypeParam = decl.
|
|
216
|
-
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
218
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
219
|
+
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
217
220
|
const prefix = decl.isExported ? "" : "private ";
|
|
218
221
|
const memberParts = [];
|
|
219
222
|
const pushMember = (node, text) => {
|
|
@@ -250,8 +253,8 @@ export class Printer {
|
|
|
250
253
|
printInterface(decl, indent) {
|
|
251
254
|
const pad = indentStr(indent);
|
|
252
255
|
const memberPad = indentStr(indent + 1);
|
|
253
|
-
const nameWithTypeParam = decl.
|
|
254
|
-
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
256
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
257
|
+
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
255
258
|
const prefix = decl.isExported ? "" : "private ";
|
|
256
259
|
if (decl.methods.length === 0)
|
|
257
260
|
return `${pad}${prefix}${header}\n${pad}}`;
|
|
@@ -279,7 +282,7 @@ export class Printer {
|
|
|
279
282
|
const pad = indentStr(indent);
|
|
280
283
|
const memberPad = indentStr(indent + 1);
|
|
281
284
|
const prefix = decl.isExported ? "" : "private ";
|
|
282
|
-
const nameWithTypeParam = decl.
|
|
285
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
283
286
|
const lines = [];
|
|
284
287
|
if (decl.hasConstructor)
|
|
285
288
|
lines.push(`${memberPad}constructor(${this.printParams(decl.ctorParams)});`);
|
package/dist/types.js
CHANGED
|
@@ -6,11 +6,11 @@ export const UNKNOWN = { kind: "unknown" };
|
|
|
6
6
|
export function arrayOf(element) {
|
|
7
7
|
return { kind: "array", element };
|
|
8
8
|
}
|
|
9
|
-
export function classType(name,
|
|
10
|
-
return
|
|
9
|
+
export function classType(name, typeArgs) {
|
|
10
|
+
return typeArgs && typeArgs.length > 0 ? { kind: "class", name, typeArgs } : { kind: "class", name };
|
|
11
11
|
}
|
|
12
|
-
export function interfaceType(name,
|
|
13
|
-
return
|
|
12
|
+
export function interfaceType(name, typeArgs) {
|
|
13
|
+
return typeArgs && typeArgs.length > 0 ? { kind: "interface", name, typeArgs } : { kind: "interface", name };
|
|
14
14
|
}
|
|
15
15
|
export function typeParamType(name) {
|
|
16
16
|
return { kind: "typeParam", name };
|
|
@@ -44,7 +44,7 @@ export function typeToString(t) {
|
|
|
44
44
|
return t.name;
|
|
45
45
|
case "class":
|
|
46
46
|
case "interface":
|
|
47
|
-
return t.
|
|
47
|
+
return t.typeArgs && t.typeArgs.length > 0 ? `${t.name}<${t.typeArgs.map(typeToString).join(", ")}>` : t.name;
|
|
48
48
|
case "function":
|
|
49
49
|
return `(${t.params.map(typeToString).join(", ")}) => ${typeToString(t.returnType)}`;
|
|
50
50
|
case "task":
|
|
@@ -91,31 +91,36 @@ export function resolveTypeNode(node, namedTypes, onNamedType) {
|
|
|
91
91
|
const inner = resolveTypeNode(node.inner, namedTypes, onNamedType);
|
|
92
92
|
return inner ? nullableOf(inner) : null;
|
|
93
93
|
}
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
94
|
+
// Resolve every type argument given, in order — a primitive/enum/
|
|
95
|
+
// type-param name below never accepts any (arity error, caught as a plain
|
|
96
|
+
// resolution failure here). Arity *against a class/interface's own
|
|
97
|
+
// declared parameter count* is a separate check the caller makes, since
|
|
98
|
+
// only it knows which names are actually declared generic and with how
|
|
99
|
+
// many parameters (see Checker.validateGenericArity).
|
|
100
|
+
let typeArgs = null;
|
|
100
101
|
if (node.typeArgs) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
typeArgs = [];
|
|
103
|
+
for (const argNode of node.typeArgs) {
|
|
104
|
+
const resolvedArg = resolveTypeNode(argNode, namedTypes, onNamedType);
|
|
105
|
+
if (!resolvedArg)
|
|
106
|
+
return null;
|
|
107
|
+
typeArgs.push(resolvedArg);
|
|
108
|
+
}
|
|
104
109
|
}
|
|
105
110
|
let resolved;
|
|
106
111
|
if (PRIMITIVE_NAMES.has(node.name)) {
|
|
107
|
-
resolved =
|
|
112
|
+
resolved = typeArgs ? null : { kind: node.name };
|
|
108
113
|
}
|
|
109
114
|
else {
|
|
110
115
|
const kind = namedTypes.get(node.name);
|
|
111
116
|
if (kind === "class")
|
|
112
|
-
resolved = classType(node.name,
|
|
117
|
+
resolved = classType(node.name, typeArgs ?? undefined);
|
|
113
118
|
else if (kind === "interface")
|
|
114
|
-
resolved = interfaceType(node.name,
|
|
119
|
+
resolved = interfaceType(node.name, typeArgs ?? undefined);
|
|
115
120
|
else if (kind === "enum")
|
|
116
|
-
resolved =
|
|
121
|
+
resolved = typeArgs ? null : enumType(node.name);
|
|
117
122
|
else if (kind === "typeParam")
|
|
118
|
-
resolved =
|
|
123
|
+
resolved = typeArgs ? null : typeParamType(node.name);
|
|
119
124
|
else
|
|
120
125
|
resolved = null;
|
|
121
126
|
}
|
|
@@ -153,12 +158,12 @@ export function typesEqual(a, b) {
|
|
|
153
158
|
const other = b;
|
|
154
159
|
if (a.name !== other.name)
|
|
155
160
|
return false;
|
|
156
|
-
if ("
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
161
|
+
if ("typeArgs" in a || "typeArgs" in other) {
|
|
162
|
+
const aArgs = a.typeArgs;
|
|
163
|
+
const bArgs = other.typeArgs;
|
|
164
|
+
if (!aArgs || !bArgs || aArgs.length !== bArgs.length)
|
|
160
165
|
return false;
|
|
161
|
-
return typesEqual(
|
|
166
|
+
return aArgs.every((t, i) => typesEqual(t, bArgs[i]));
|
|
162
167
|
}
|
|
163
168
|
return true;
|
|
164
169
|
}
|
package/package.json
CHANGED