kopscript 0.2.0 → 0.3.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/parser.js CHANGED
@@ -153,7 +153,8 @@ export class Parser {
153
153
  this.advance();
154
154
  }
155
155
  const type = this.parseType();
156
- const name = this.consume(TokenKind.Identifier, "Expected name").lexeme;
156
+ const nameTok = this.consume(TokenKind.Identifier, "Expected name");
157
+ const name = nameTok.lexeme;
157
158
  if (this.check(TokenKind.LParen)) {
158
159
  const params = this.parseParamList();
159
160
  const body = this.parseBlock();
@@ -165,7 +166,7 @@ export class Parser {
165
166
  this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
166
167
  const init = this.parseExpression();
167
168
  this.consume(TokenKind.Semicolon, "Expected ';' after variable declaration");
168
- return { kind: "VarDecl", isConst: false, name, type, init, line: start.line, col: start.col };
169
+ return { kind: "VarDecl", isConst: false, name, nameLine: nameTok.line, nameCol: nameTok.col, type, init, line: start.line, col: start.col };
169
170
  }
170
171
  // Parses `Type name = init` without consuming a trailing terminator — used
171
172
  // both for top-level/block statements (which add the `;`) and for-loop
@@ -173,18 +174,18 @@ export class Parser {
173
174
  parseVarDeclHeader() {
174
175
  const start = this.peek();
175
176
  const type = this.parseType();
176
- const name = this.consume(TokenKind.Identifier, "Expected variable name").lexeme;
177
+ const nameTok = this.consume(TokenKind.Identifier, "Expected variable name");
177
178
  this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
178
179
  const init = this.parseExpression();
179
- return { kind: "VarDecl", isConst: false, name, type, init, line: start.line, col: start.col };
180
+ return { kind: "VarDecl", isConst: false, name: nameTok.lexeme, nameLine: nameTok.line, nameCol: nameTok.col, type, init, line: start.line, col: start.col };
180
181
  }
181
182
  parseConstDecl() {
182
183
  const start = this.advance(); // 'const'
183
184
  const type = this.parseType();
184
- const name = this.consume(TokenKind.Identifier, "Expected constant name").lexeme;
185
+ const nameTok = this.consume(TokenKind.Identifier, "Expected constant name");
185
186
  this.consume(TokenKind.Assign, "Expected '=' in constant declaration");
186
187
  const init = this.parseExpression();
187
- return { kind: "VarDecl", isConst: true, name, type, init, line: start.line, col: start.col };
188
+ return { kind: "VarDecl", isConst: true, name: nameTok.lexeme, nameLine: nameTok.line, nameCol: nameTok.col, type, init, line: start.line, col: start.col };
188
189
  }
189
190
  parseParamList() {
190
191
  this.consume(TokenKind.LParen, "Expected '('");
@@ -260,7 +261,8 @@ export class Parser {
260
261
  }
261
262
  const memberStart = this.peek();
262
263
  const type = this.parseType();
263
- const memberName = this.consume(TokenKind.Identifier, "Expected field, property, or method name").lexeme;
264
+ const memberNameTok = this.consume(TokenKind.Identifier, "Expected field, property, or method name");
265
+ const memberName = memberNameTok.lexeme;
264
266
  if (this.check(TokenKind.LParen)) {
265
267
  const params = this.parseParamList();
266
268
  const body = this.parseBlock();
@@ -275,6 +277,8 @@ export class Parser {
275
277
  isVirtual,
276
278
  isOverride,
277
279
  name: memberName,
280
+ nameLine: memberNameTok.line,
281
+ nameCol: memberNameTok.col,
278
282
  params,
279
283
  returnType: type,
280
284
  body,
@@ -307,6 +311,8 @@ export class Parser {
307
311
  visibility,
308
312
  hasSetter,
309
313
  name: memberName,
314
+ nameLine: memberNameTok.line,
315
+ nameCol: memberNameTok.col,
310
316
  type,
311
317
  line: memberStart.line,
312
318
  col: memberStart.col,
@@ -331,6 +337,8 @@ export class Parser {
331
337
  isStatic,
332
338
  initializer,
333
339
  name: memberName,
340
+ nameLine: memberNameTok.line,
341
+ nameCol: memberNameTok.col,
334
342
  type,
335
343
  line: memberStart.line,
336
344
  col: memberStart.col,
@@ -354,10 +362,18 @@ export class Parser {
354
362
  while (!this.check(TokenKind.RBrace) && !this.check(TokenKind.EOF)) {
355
363
  const memberStart = this.peek();
356
364
  const returnType = this.parseType();
357
- const methodName = this.consume(TokenKind.Identifier, "Expected method name").lexeme;
365
+ const methodNameTok = this.consume(TokenKind.Identifier, "Expected method name");
358
366
  const params = this.parseParamList();
359
367
  this.consume(TokenKind.Semicolon, "Expected ';' after interface method signature");
360
- methods.push({ name: methodName, params, returnType, line: memberStart.line, col: memberStart.col });
368
+ methods.push({
369
+ name: methodNameTok.lexeme,
370
+ nameLine: methodNameTok.line,
371
+ nameCol: methodNameTok.col,
372
+ params,
373
+ returnType,
374
+ line: memberStart.line,
375
+ col: memberStart.col,
376
+ });
361
377
  }
362
378
  this.consume(TokenKind.RBrace, "Expected '}' after interface body");
363
379
  return { kind: "InterfaceDecl", isExported, name, baseList, methods, line: start.line, col: start.col };
@@ -604,8 +620,8 @@ export class Parser {
604
620
  type = this.parseFunctionType();
605
621
  }
606
622
  else if (this.check(TokenKind.Task)) {
607
- this.advance();
608
- let resultType = { kind: "NamedType", name: "void" };
623
+ const taskTok = this.advance();
624
+ let resultType = { kind: "NamedType", name: "void", line: taskTok.line, col: taskTok.col };
609
625
  if (this.match(TokenKind.Lt)) {
610
626
  resultType = this.parseType();
611
627
  this.consume(TokenKind.Gt, "Expected '>' after task result type");
@@ -621,12 +637,21 @@ export class Parser {
621
637
  }
622
638
  else {
623
639
  const nameToken = this.check(TokenKind.Void) ? this.advance() : this.consume(TokenKind.Identifier, "Expected type name");
624
- type = { kind: "NamedType", name: nameToken.lexeme };
640
+ type = { kind: "NamedType", name: nameToken.lexeme, line: nameToken.line, col: nameToken.col };
625
641
  }
626
- while (this.check(TokenKind.LBracket)) {
627
- this.advance();
628
- this.consume(TokenKind.RBracket, "Expected ']' after '[' in array type");
629
- type = { kind: "ArrayType", element: type };
642
+ // `[]` and `?` are both postfix and can alternate in either order —
643
+ // `string?[]` (array of nullable strings) and `string[]?` (nullable
644
+ // array of strings) parse with the nesting their order implies.
645
+ while (this.check(TokenKind.LBracket) || this.check(TokenKind.Question)) {
646
+ if (this.check(TokenKind.LBracket)) {
647
+ this.advance();
648
+ this.consume(TokenKind.RBracket, "Expected ']' after '[' in array type");
649
+ type = { kind: "ArrayType", element: type };
650
+ }
651
+ else {
652
+ const q = this.advance();
653
+ type = { kind: "NullableType", inner: type, line: q.line, col: q.col };
654
+ }
630
655
  }
631
656
  return type;
632
657
  }
@@ -779,6 +804,10 @@ export class Parser {
779
804
  this.advance();
780
805
  return { kind: "BoolLiteral", value: t.kind === TokenKind.True, line: t.line, col: t.col };
781
806
  }
807
+ if (this.check(TokenKind.Null)) {
808
+ this.advance();
809
+ return { kind: "NullLiteral", line: t.line, col: t.col };
810
+ }
782
811
  if (this.check(TokenKind.This)) {
783
812
  this.advance();
784
813
  return { kind: "ThisExpr", line: t.line, col: t.col };
@@ -848,8 +877,18 @@ export class Parser {
848
877
  if (startKind !== TokenKind.Identifier && startKind !== TokenKind.Void)
849
878
  return false;
850
879
  i++;
851
- while (this.tokens[i]?.kind === TokenKind.LBracket && this.tokens[i + 1]?.kind === TokenKind.RBracket) {
852
- i += 2;
880
+ // Mirrors parseType's postfix loop: `[]` and `?` can alternate in
881
+ // either order (`string?[]`, `string[]?`) after the base type name.
882
+ while (true) {
883
+ if (this.tokens[i]?.kind === TokenKind.LBracket && this.tokens[i + 1]?.kind === TokenKind.RBracket) {
884
+ i += 2;
885
+ }
886
+ else if (this.tokens[i]?.kind === TokenKind.Question) {
887
+ i += 1;
888
+ }
889
+ else {
890
+ break;
891
+ }
853
892
  }
854
893
  return this.tokens[i]?.kind === TokenKind.Identifier;
855
894
  }
package/dist/tokens.js CHANGED
@@ -8,6 +8,7 @@ export var TokenKind;
8
8
  TokenKind["Identifier"] = "Identifier";
9
9
  TokenKind["True"] = "True";
10
10
  TokenKind["False"] = "False";
11
+ TokenKind["Null"] = "Null";
11
12
  // Keywords
12
13
  TokenKind["Using"] = "Using";
13
14
  TokenKind["Extern"] = "Extern";
@@ -62,6 +63,7 @@ export var TokenKind;
62
63
  TokenKind["Dot"] = "Dot";
63
64
  TokenKind["Arrow"] = "Arrow";
64
65
  TokenKind["Underscore"] = "Underscore";
66
+ TokenKind["Question"] = "Question";
65
67
  // Operators
66
68
  TokenKind["Plus"] = "Plus";
67
69
  TokenKind["Minus"] = "Minus";
package/dist/types.js CHANGED
@@ -24,6 +24,9 @@ export function taskType(resultType) {
24
24
  export function stateType(valueType) {
25
25
  return { kind: "state", valueType };
26
26
  }
27
+ export function nullableOf(inner) {
28
+ return inner.kind === "nullable" ? inner : { kind: "nullable", inner };
29
+ }
27
30
  export function typeToString(t) {
28
31
  switch (t.kind) {
29
32
  case "number":
@@ -44,46 +47,55 @@ export function typeToString(t) {
44
47
  return t.resultType.kind === "void" ? "task" : `task<${typeToString(t.resultType)}>`;
45
48
  case "state":
46
49
  return `state<${typeToString(t.valueType)}>`;
50
+ case "nullable":
51
+ return `${typeToString(t.inner)}?`;
47
52
  }
48
53
  }
49
54
  const PRIMITIVE_NAMES = new Set(["number", "string", "bool", "void"]);
50
55
  // Resolves a syntactic type annotation to a Type, given the kind of every known
51
- // user-declared name (class, interface, or enum).
52
- export function resolveTypeNode(node, namedTypes) {
56
+ // user-declared name (class, interface, or enum). `onNamedType`, when given, is
57
+ // called with every `NamedType` leaf visited and the Type it resolved to — a
58
+ // hook for tools (e.g. hover) that want to know what a type annotation in
59
+ // source actually refers to, without duplicating this resolution logic.
60
+ export function resolveTypeNode(node, namedTypes, onNamedType) {
53
61
  if (node.kind === "ArrayType") {
54
- const element = resolveTypeNode(node.element, namedTypes);
62
+ const element = resolveTypeNode(node.element, namedTypes, onNamedType);
55
63
  return element ? arrayOf(element) : null;
56
64
  }
57
65
  if (node.kind === "FunctionType") {
58
66
  const params = [];
59
67
  for (const p of node.params) {
60
- const resolved = resolveTypeNode(p, namedTypes);
68
+ const resolved = resolveTypeNode(p, namedTypes, onNamedType);
61
69
  if (!resolved)
62
70
  return null;
63
71
  params.push(resolved);
64
72
  }
65
- const returnType = resolveTypeNode(node.returnType, namedTypes);
73
+ const returnType = resolveTypeNode(node.returnType, namedTypes, onNamedType);
66
74
  return returnType ? functionType(params, returnType) : null;
67
75
  }
68
76
  if (node.kind === "TaskType") {
69
- const resultType = resolveTypeNode(node.resultType, namedTypes);
77
+ const resultType = resolveTypeNode(node.resultType, namedTypes, onNamedType);
70
78
  return resultType ? taskType(resultType) : null;
71
79
  }
72
80
  if (node.kind === "StateType") {
73
- const valueType = resolveTypeNode(node.valueType, namedTypes);
81
+ const valueType = resolveTypeNode(node.valueType, namedTypes, onNamedType);
74
82
  return valueType ? stateType(valueType) : null;
75
83
  }
84
+ if (node.kind === "NullableType") {
85
+ const inner = resolveTypeNode(node.inner, namedTypes, onNamedType);
86
+ return inner ? nullableOf(inner) : null;
87
+ }
88
+ let resolved;
76
89
  if (PRIMITIVE_NAMES.has(node.name)) {
77
- return { kind: node.name };
90
+ resolved = { kind: node.name };
78
91
  }
79
- const kind = namedTypes.get(node.name);
80
- if (kind === "class")
81
- return classType(node.name);
82
- if (kind === "interface")
83
- return interfaceType(node.name);
84
- if (kind === "enum")
85
- return enumType(node.name);
86
- return null;
92
+ else {
93
+ const kind = namedTypes.get(node.name);
94
+ resolved = kind === "class" ? classType(node.name) : kind === "interface" ? interfaceType(node.name) : kind === "enum" ? enumType(node.name) : null;
95
+ }
96
+ if (resolved)
97
+ onNamedType?.(node, resolved);
98
+ return resolved;
87
99
  }
88
100
  export function typesEqual(a, b) {
89
101
  if (a.kind === "unknown" || b.kind === "unknown")
@@ -101,6 +113,9 @@ export function typesEqual(a, b) {
101
113
  if (a.kind === "state" && b.kind === "state") {
102
114
  return typesEqual(a.valueType, b.valueType);
103
115
  }
116
+ if (a.kind === "nullable" && b.kind === "nullable") {
117
+ return typesEqual(a.inner, b.inner);
118
+ }
104
119
  if ((a.kind === "class" || a.kind === "interface" || a.kind === "enum") && "name" in b) {
105
120
  return a.name === b.name;
106
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopscript",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "KopScript: a small OOP, strongly-typed language that transpiles to JavaScript",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,7 +20,8 @@
20
20
  "files": [
21
21
  "dist",
22
22
  "bin",
23
- "assets"
23
+ "assets",
24
+ "LLM.md"
24
25
  ],
25
26
  "bin": {
26
27
  "ks": "bin/ks.js"