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 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 the whole feature.
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
- const fields = new Map();
173
- const staticFields = new Map();
174
- for (const p of decl.properties) {
175
- const info = { type: this.resolveType(p.type, decl.line, decl.col), visibility: "public", hasSetter: p.hasSetter };
176
- (p.isStatic ? staticFields : fields).set(p.name, info);
177
- }
178
- const methods = new Map();
179
- const staticMethods = new Map();
180
- for (const m of decl.methods) {
181
- const info = {
182
- params: m.params.map((p) => this.resolveType(p.type, decl.line, decl.col)),
183
- returnType: this.resolveType(m.returnType, decl.line, decl.col),
184
- visibility: "public",
185
- isVirtual: m.isVirtual,
186
- isOverride: false,
187
- };
188
- (m.isStatic ? staticMethods : methods).set(m.name, info);
189
- }
190
- const ownCtorParams = decl.hasConstructor ? decl.ctorParams.map((p) => this.resolveType(p.type, decl.line, decl.col)) : null;
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: null,
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
- value += this.readStringChar();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopscript",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",