kopscript 0.1.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/modules.js CHANGED
@@ -29,7 +29,7 @@ export function loadModuleGraph(entryAbsPath) {
29
29
  const diagnostics = new DiagnosticBag();
30
30
  const tokens = new Lexer(source, diagnostics).tokenize();
31
31
  const program = new Parser(tokens, diagnostics).parseProgram();
32
- const record = { absPath, source, program, diagnostics, dependencies: [] };
32
+ const record = { absPath, source, program, diagnostics, dependencies: [], hoverEntries: [] };
33
33
  modules.set(absPath, record);
34
34
  stack.push(absPath);
35
35
  for (const u of program.usings) {
@@ -66,6 +66,7 @@ export function compileGraph(entryAbsPath) {
66
66
  return { success: false, entryMissing: true, modules, order, outputs: new Map() };
67
67
  }
68
68
  const exportsByModule = new Map();
69
+ const rawContentsByModule = new Map();
69
70
  let hasErrors = false;
70
71
  for (const absPath of order) {
71
72
  const mod = modules.get(absPath);
@@ -104,11 +105,13 @@ export function compileGraph(entryAbsPath) {
104
105
  for (const [name, info] of depExports.enums)
105
106
  merged.enums.set(name, info);
106
107
  }
107
- const checker = new Checker(mod.program, mod.diagnostics, merged);
108
+ const checker = new Checker(mod.program, mod.diagnostics, merged, absPath);
108
109
  checker.check();
110
+ mod.hoverEntries = checker.hoverEntries;
109
111
  if (mod.diagnostics.hasErrors)
110
112
  hasErrors = true;
111
113
  exportsByModule.set(absPath, checker.getExports());
114
+ rawContentsByModule.set(absPath, checker.getRawContents());
112
115
  }
113
116
  if (hasErrors) {
114
117
  return { success: false, entryMissing: false, modules, order, outputs: new Map() };
@@ -127,7 +130,7 @@ export function compileGraph(entryAbsPath) {
127
130
  usingExports.set(u.path, [...importableTypeNames, ...depExports.functions.keys(), ...depExports.externValues.keys()]);
128
131
  }
129
132
  }
130
- outputs.set(absPath, new CodeGenerator().generate(mod.program, usingExports));
133
+ outputs.set(absPath, new CodeGenerator().generate(mod.program, usingExports, rawContentsByModule.get(absPath) ?? new Map()));
131
134
  }
132
135
  return { success: true, entryMissing: false, modules, order, outputs };
133
136
  }
package/dist/parser.js CHANGED
@@ -50,6 +50,8 @@ export class Parser {
50
50
  return this.parseEnumDecl(true);
51
51
  if (this.check(TokenKind.Extern))
52
52
  return this.parseExternDecl(true);
53
+ if (this.check(TokenKind.Raw))
54
+ return this.parseRawStringDecl(true);
53
55
  if (this.check(TokenKind.LBrace))
54
56
  return this.parseBlock();
55
57
  if (this.check(TokenKind.If))
@@ -102,6 +104,8 @@ export class Parser {
102
104
  return this.parseEnumDecl(isExported);
103
105
  if (this.check(TokenKind.Extern))
104
106
  return this.parseExternDecl(isExported);
107
+ if (this.check(TokenKind.Raw))
108
+ return this.parseRawStringDecl(isExported);
105
109
  if (this.isDeclStart()) {
106
110
  const decl = this.parseDeclaration(isExported);
107
111
  if (decl.kind === "VarDecl") {
@@ -149,7 +153,8 @@ export class Parser {
149
153
  this.advance();
150
154
  }
151
155
  const type = this.parseType();
152
- const name = this.consume(TokenKind.Identifier, "Expected name").lexeme;
156
+ const nameTok = this.consume(TokenKind.Identifier, "Expected name");
157
+ const name = nameTok.lexeme;
153
158
  if (this.check(TokenKind.LParen)) {
154
159
  const params = this.parseParamList();
155
160
  const body = this.parseBlock();
@@ -161,7 +166,7 @@ export class Parser {
161
166
  this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
162
167
  const init = this.parseExpression();
163
168
  this.consume(TokenKind.Semicolon, "Expected ';' after variable declaration");
164
- 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 };
165
170
  }
166
171
  // Parses `Type name = init` without consuming a trailing terminator — used
167
172
  // both for top-level/block statements (which add the `;`) and for-loop
@@ -169,18 +174,18 @@ export class Parser {
169
174
  parseVarDeclHeader() {
170
175
  const start = this.peek();
171
176
  const type = this.parseType();
172
- const name = this.consume(TokenKind.Identifier, "Expected variable name").lexeme;
177
+ const nameTok = this.consume(TokenKind.Identifier, "Expected variable name");
173
178
  this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
174
179
  const init = this.parseExpression();
175
- 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 };
176
181
  }
177
182
  parseConstDecl() {
178
183
  const start = this.advance(); // 'const'
179
184
  const type = this.parseType();
180
- const name = this.consume(TokenKind.Identifier, "Expected constant name").lexeme;
185
+ const nameTok = this.consume(TokenKind.Identifier, "Expected constant name");
181
186
  this.consume(TokenKind.Assign, "Expected '=' in constant declaration");
182
187
  const init = this.parseExpression();
183
- 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 };
184
189
  }
185
190
  parseParamList() {
186
191
  this.consume(TokenKind.LParen, "Expected '('");
@@ -256,7 +261,8 @@ export class Parser {
256
261
  }
257
262
  const memberStart = this.peek();
258
263
  const type = this.parseType();
259
- 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;
260
266
  if (this.check(TokenKind.LParen)) {
261
267
  const params = this.parseParamList();
262
268
  const body = this.parseBlock();
@@ -271,6 +277,8 @@ export class Parser {
271
277
  isVirtual,
272
278
  isOverride,
273
279
  name: memberName,
280
+ nameLine: memberNameTok.line,
281
+ nameCol: memberNameTok.col,
274
282
  params,
275
283
  returnType: type,
276
284
  body,
@@ -303,6 +311,8 @@ export class Parser {
303
311
  visibility,
304
312
  hasSetter,
305
313
  name: memberName,
314
+ nameLine: memberNameTok.line,
315
+ nameCol: memberNameTok.col,
306
316
  type,
307
317
  line: memberStart.line,
308
318
  col: memberStart.col,
@@ -327,6 +337,8 @@ export class Parser {
327
337
  isStatic,
328
338
  initializer,
329
339
  name: memberName,
340
+ nameLine: memberNameTok.line,
341
+ nameCol: memberNameTok.col,
330
342
  type,
331
343
  line: memberStart.line,
332
344
  col: memberStart.col,
@@ -350,10 +362,18 @@ export class Parser {
350
362
  while (!this.check(TokenKind.RBrace) && !this.check(TokenKind.EOF)) {
351
363
  const memberStart = this.peek();
352
364
  const returnType = this.parseType();
353
- const methodName = this.consume(TokenKind.Identifier, "Expected method name").lexeme;
365
+ const methodNameTok = this.consume(TokenKind.Identifier, "Expected method name");
354
366
  const params = this.parseParamList();
355
367
  this.consume(TokenKind.Semicolon, "Expected ';' after interface method signature");
356
- 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
+ });
357
377
  }
358
378
  this.consume(TokenKind.RBrace, "Expected '}' after interface body");
359
379
  return { kind: "InterfaceDecl", isExported, name, baseList, methods, line: start.line, col: start.col };
@@ -405,6 +425,22 @@ export class Parser {
405
425
  }
406
426
  return { modulePath, jsName };
407
427
  }
428
+ // `raw string <Name> from "<path>";` — embeds a real file's contents as a
429
+ // JS string constant at compile time. Type must be literally 'string' (no
430
+ // other type makes sense for file contents); no 'as' clause — there's no
431
+ // JS-side name to alias, unlike extern.
432
+ parseRawStringDecl(isExported) {
433
+ const start = this.advance(); // 'raw'
434
+ const typeTok = this.consume(TokenKind.Identifier, "Expected 'string' after 'raw'");
435
+ if (typeTok.lexeme !== "string") {
436
+ this.diagnostics.error(`'raw' declarations must be of type 'string', got '${typeTok.lexeme}'`, typeTok.line, typeTok.col);
437
+ }
438
+ const name = this.consume(TokenKind.Identifier, "Expected name").lexeme;
439
+ this.consume(TokenKind.From, "Expected 'from \"<path>\"' after 'raw string <Name>'");
440
+ const pathTok = this.consume(TokenKind.String, "Expected a file path string after 'from'");
441
+ this.consume(TokenKind.Semicolon, "Expected ';' after 'raw' declaration");
442
+ return { kind: "RawStringDecl", isExported, name, path: pathTok.lexeme, line: start.line, col: start.col };
443
+ }
408
444
  parseExternClassBody(start, isExported) {
409
445
  const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
410
446
  this.consume(TokenKind.LBrace, "Expected '{' before extern class body");
@@ -584,8 +620,8 @@ export class Parser {
584
620
  type = this.parseFunctionType();
585
621
  }
586
622
  else if (this.check(TokenKind.Task)) {
587
- this.advance();
588
- let resultType = { kind: "NamedType", name: "void" };
623
+ const taskTok = this.advance();
624
+ let resultType = { kind: "NamedType", name: "void", line: taskTok.line, col: taskTok.col };
589
625
  if (this.match(TokenKind.Lt)) {
590
626
  resultType = this.parseType();
591
627
  this.consume(TokenKind.Gt, "Expected '>' after task result type");
@@ -601,12 +637,21 @@ export class Parser {
601
637
  }
602
638
  else {
603
639
  const nameToken = this.check(TokenKind.Void) ? this.advance() : this.consume(TokenKind.Identifier, "Expected type name");
604
- type = { kind: "NamedType", name: nameToken.lexeme };
640
+ type = { kind: "NamedType", name: nameToken.lexeme, line: nameToken.line, col: nameToken.col };
605
641
  }
606
- while (this.check(TokenKind.LBracket)) {
607
- this.advance();
608
- this.consume(TokenKind.RBracket, "Expected ']' after '[' in array type");
609
- 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
+ }
610
655
  }
611
656
  return type;
612
657
  }
@@ -759,6 +804,10 @@ export class Parser {
759
804
  this.advance();
760
805
  return { kind: "BoolLiteral", value: t.kind === TokenKind.True, line: t.line, col: t.col };
761
806
  }
807
+ if (this.check(TokenKind.Null)) {
808
+ this.advance();
809
+ return { kind: "NullLiteral", line: t.line, col: t.col };
810
+ }
762
811
  if (this.check(TokenKind.This)) {
763
812
  this.advance();
764
813
  return { kind: "ThisExpr", line: t.line, col: t.col };
@@ -828,8 +877,18 @@ export class Parser {
828
877
  if (startKind !== TokenKind.Identifier && startKind !== TokenKind.Void)
829
878
  return false;
830
879
  i++;
831
- while (this.tokens[i]?.kind === TokenKind.LBracket && this.tokens[i + 1]?.kind === TokenKind.RBracket) {
832
- 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
+ }
833
892
  }
834
893
  return this.tokens[i]?.kind === TokenKind.Identifier;
835
894
  }
@@ -976,6 +1035,7 @@ export class Parser {
976
1035
  this.check(TokenKind.Interface) ||
977
1036
  this.check(TokenKind.Enum) ||
978
1037
  this.check(TokenKind.Extern) ||
1038
+ this.check(TokenKind.Raw) ||
979
1039
  this.check(TokenKind.Const) ||
980
1040
  this.check(TokenKind.If) ||
981
1041
  this.check(TokenKind.While) ||
package/dist/tokens.js CHANGED
@@ -8,9 +8,11 @@ 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";
15
+ TokenKind["Raw"] = "Raw";
14
16
  TokenKind["From"] = "From";
15
17
  TokenKind["As"] = "As";
16
18
  TokenKind["Const"] = "Const";
@@ -61,6 +63,7 @@ export var TokenKind;
61
63
  TokenKind["Dot"] = "Dot";
62
64
  TokenKind["Arrow"] = "Arrow";
63
65
  TokenKind["Underscore"] = "Underscore";
66
+ TokenKind["Question"] = "Question";
64
67
  // Operators
65
68
  TokenKind["Plus"] = "Plus";
66
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.1.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",
@@ -19,7 +19,9 @@
19
19
  "main": "./dist/modules.js",
20
20
  "files": [
21
21
  "dist",
22
- "bin"
22
+ "bin",
23
+ "assets",
24
+ "LLM.md"
23
25
  ],
24
26
  "bin": {
25
27
  "ks": "bin/ks.js"