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/LLM.md +377 -0
- package/README.md +87 -22
- package/dist/checker.js +155 -16
- package/dist/cli.js +85 -16
- package/dist/codegen.js +2 -0
- package/dist/lexer.js +3 -0
- package/dist/modules.js +2 -1
- package/dist/parser.js +57 -18
- package/dist/tokens.js +2 -0
- package/dist/types.js +31 -16
- package/package.json +3 -2
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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({
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
852
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
resolved = { kind: node.name };
|
|
78
91
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.
|
|
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"
|