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/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((n) => !this.interfaceNames.has(n)) ?? null;
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
- // `<T>` right after a class/interface name — a single, unconstrained type
247
- // parameter (v1 has no `Map<K, V>`, no `T : IFoo` constraints). Null if
248
- // absent, the overwhelmingly common case.
249
- parseOptionalTypeParam() {
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 null;
252
- const name = this.consume(TokenKind.Identifier, "Expected a type parameter name").lexeme;
253
- this.consume(TokenKind.Gt, "Expected '>' after type parameter");
254
- return name;
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 typeParam = this.parseOptionalTypeParam();
279
+ const typeParams = this.parseTypeParamList();
260
280
  const baseList = [];
261
281
  if (this.match(TokenKind.Colon)) {
262
282
  do {
263
- baseList.push(this.consume(TokenKind.Identifier, "Expected base class or interface name").lexeme);
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, typeParam, baseList, fields, properties, constructor: ctor, methods, template, line: start.line, col: start.col };
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 typeParam = this.parseOptionalTypeParam();
440
+ const typeParams = this.parseTypeParamList();
421
441
  const baseList = [];
422
442
  if (this.match(TokenKind.Colon)) {
423
443
  do {
424
- baseList.push(this.consume(TokenKind.Identifier, "Expected base interface name").lexeme);
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, typeParam, baseList, methods, line: start.line, col: start.col };
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 typeParam = this.parseOptionalTypeParam();
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, typeParam, jsName, hasConstructor, ctorParams, properties, methods, modulePath, line: start.line, col: start.col };
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
- // `Box<number>` — a generic type reference. At most one type argument
709
- // in v1 (matching ClassDecl/InterfaceDecl's single type parameter);
710
- // whether `name` actually refers to a declared generic type is a
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.consume(TokenKind.Gt, "Expected '>' after type argument");
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 Box<number>(...)` — unambiguous here: `new <Identifier>` is
897
- // always followed by `(`, generic type args or not, so seeing `<`
898
- // instead can only mean a type argument list, never a comparison.
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.consume(TokenKind.Gt, "Expected '>' after type argument");
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.typeParam ? `${decl.name}<${decl.typeParam}>` : decl.name;
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.typeParam ? `${decl.name}<${decl.typeParam}>` : decl.name;
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.typeParam ? `${decl.name}<${decl.typeParam}>` : decl.name;
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, typeArg) {
10
- return typeArg ? { kind: "class", name, typeArg } : { kind: "class", name };
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, typeArg) {
13
- return typeArg ? { kind: "interface", name, typeArg } : { kind: "interface", name };
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.typeArg ? `${t.name}<${typeToString(t.typeArg)}>` : t.name;
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
- // At most one type argument in v1resolve it once, up front; a
95
- // primitive/enum/type-param name below never accepts one (arity error,
96
- // caught as a plain resolution failure here a class/interface's own
97
- // required-vs-supplied arity mismatch is a separate check the caller
98
- // makes, since only it knows which names are actually declared generic).
99
- let typeArg = null;
94
+ // Resolve every type argument given, in ordera 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
- typeArg = resolveTypeNode(node.typeArgs[0], namedTypes, onNamedType);
102
- if (!typeArg)
103
- return null;
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 = typeArg ? null : { kind: node.name };
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, typeArg ?? undefined);
117
+ resolved = classType(node.name, typeArgs ?? undefined);
113
118
  else if (kind === "interface")
114
- resolved = interfaceType(node.name, typeArg ?? undefined);
119
+ resolved = interfaceType(node.name, typeArgs ?? undefined);
115
120
  else if (kind === "enum")
116
- resolved = typeArg ? null : enumType(node.name);
121
+ resolved = typeArgs ? null : enumType(node.name);
117
122
  else if (kind === "typeParam")
118
- resolved = typeArg ? null : typeParamType(node.name);
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 ("typeArg" in a || "typeArg" in other) {
157
- const aArg = a.typeArg;
158
- const bArg = other.typeArg;
159
- if (!aArg || !bArg)
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(aArg, bArg);
166
+ return aArgs.every((t, i) => typesEqual(t, bArgs[i]));
162
167
  }
163
168
  return true;
164
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopscript",
3
- "version": "0.11.1",
3
+ "version": "0.13.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",