kopscript 0.7.2 → 0.9.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
@@ -16,7 +16,7 @@ export class Parser {
16
16
  while (!this.check(TokenKind.EOF)) {
17
17
  if (this.check(TokenKind.Using)) {
18
18
  const t = this.peek();
19
- this.diagnostics.error("'using' directives must appear at the top of the file, before any other declaration", t.line, t.col);
19
+ this.diagnostics.error("KS2001", "'using' directives must appear at the top of the file, before any other declaration", t.line, t.col);
20
20
  this.parseUsing(); // consume and discard so parsing can continue
21
21
  continue;
22
22
  }
@@ -44,7 +44,7 @@ export class Parser {
44
44
  const expr = this.parseExpression();
45
45
  if (!this.check(TokenKind.EOF)) {
46
46
  const t = this.peek();
47
- this.diagnostics.error(`Unexpected token '${t.lexeme}' after expression`, t.line, t.col);
47
+ this.diagnostics.error("KS2002", `Unexpected token '${t.lexeme}' after expression`, t.line, t.col);
48
48
  }
49
49
  return expr;
50
50
  }
@@ -60,7 +60,7 @@ export class Parser {
60
60
  const type = this.parseType();
61
61
  if (!this.check(TokenKind.EOF)) {
62
62
  const t = this.peek();
63
- this.diagnostics.error(`Unexpected token '${t.lexeme}' after type`, t.line, t.col);
63
+ this.diagnostics.error("KS2003", `Unexpected token '${t.lexeme}' after type`, t.line, t.col);
64
64
  }
65
65
  return type;
66
66
  }
@@ -152,11 +152,11 @@ export class Parser {
152
152
  if (this.isDeclStart()) {
153
153
  const decl = this.parseDeclaration(isExported);
154
154
  if (decl.kind === "VarDecl") {
155
- this.diagnostics.error("'public'/'private' cannot modify a variable declaration", modifierTok.line, modifierTok.col);
155
+ this.diagnostics.error("KS2004", "'public'/'private' cannot modify a variable declaration", modifierTok.line, modifierTok.col);
156
156
  }
157
157
  return decl;
158
158
  }
159
- this.diagnostics.error("Expected a class, interface, enum, or function declaration after 'public'/'private'", modifierTok.line, modifierTok.col);
159
+ this.diagnostics.error("KS2005", "Expected a class, interface, enum, or function declaration after 'public'/'private'", modifierTok.line, modifierTok.col);
160
160
  throw new ParseError();
161
161
  }
162
162
  // Lookahead for the C#-style `Type name` declaration shape (locals and
@@ -204,7 +204,7 @@ export class Parser {
204
204
  return { kind: "FunctionDecl", isExported, isAsync, name, params, returnType: type, body, line: start.line, col: start.col };
205
205
  }
206
206
  if (isAsync) {
207
- this.diagnostics.error("'async' cannot modify a variable declaration", start.line, start.col);
207
+ this.diagnostics.error("KS2006", "'async' cannot modify a variable declaration", start.line, start.col);
208
208
  }
209
209
  this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
210
210
  const init = this.parseExpression();
@@ -273,7 +273,7 @@ export class Parser {
273
273
  if (this.check(TokenKind.Template)) {
274
274
  const templateStart = this.advance();
275
275
  if (template) {
276
- this.diagnostics.error(`Class '${name}' already has a 'template' declaration`, templateStart.line, templateStart.col);
276
+ this.diagnostics.error("KS2007", `Class '${name}' already has a 'template' declaration`, templateStart.line, templateStart.col);
277
277
  }
278
278
  this.consume(TokenKind.From, "Expected 'from \"<path>\"' after 'template'");
279
279
  const pathTok = this.consume(TokenKind.String, "Expected a file path string after 'from'");
@@ -333,7 +333,7 @@ export class Parser {
333
333
  const params = this.parseParamList();
334
334
  const body = this.parseBlock();
335
335
  if (isStatic && (isVirtual || isOverride)) {
336
- this.diagnostics.error("'static' methods cannot be 'virtual' or 'override'", memberStart.line, memberStart.col);
336
+ this.diagnostics.error("KS2008", "'static' methods cannot be 'virtual' or 'override'", memberStart.line, memberStart.col);
337
337
  }
338
338
  methods.push({
339
339
  kind: "MethodDecl",
@@ -354,13 +354,13 @@ export class Parser {
354
354
  }
355
355
  else if (this.check(TokenKind.LBrace)) {
356
356
  if (isVirtual || isOverride) {
357
- this.diagnostics.error("'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
357
+ this.diagnostics.error("KS2009", "'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
358
358
  }
359
359
  if (isAsync) {
360
- this.diagnostics.error("'async' is only valid on methods", memberStart.line, memberStart.col);
360
+ this.diagnostics.error("KS2010", "'async' is only valid on methods", memberStart.line, memberStart.col);
361
361
  }
362
362
  if (isStatic) {
363
- this.diagnostics.error("'static' properties are not supported in v1 (use a static field instead)", memberStart.line, memberStart.col);
363
+ this.diagnostics.error("KS2011", "'static' properties are not supported in v1 (use a static field instead)", memberStart.line, memberStart.col);
364
364
  }
365
365
  this.advance(); // '{'
366
366
  this.consume(TokenKind.Get, "Expected 'get' in property accessor list");
@@ -386,10 +386,10 @@ export class Parser {
386
386
  }
387
387
  else {
388
388
  if (isVirtual || isOverride) {
389
- this.diagnostics.error("'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
389
+ this.diagnostics.error("KS2012", "'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
390
390
  }
391
391
  if (isAsync) {
392
- this.diagnostics.error("'async' is only valid on methods", memberStart.line, memberStart.col);
392
+ this.diagnostics.error("KS2013", "'async' is only valid on methods", memberStart.line, memberStart.col);
393
393
  }
394
394
  let initializer = null;
395
395
  if (isStatic) {
@@ -500,7 +500,7 @@ export class Parser {
500
500
  const start = this.advance(); // 'raw'
501
501
  const typeTok = this.consume(TokenKind.Identifier, "Expected 'string' after 'raw'");
502
502
  if (typeTok.lexeme !== "string") {
503
- this.diagnostics.error(`'raw' declarations must be of type 'string', got '${typeTok.lexeme}'`, typeTok.line, typeTok.col);
503
+ this.diagnostics.error("KS2014", `'raw' declarations must be of type 'string', got '${typeTok.lexeme}'`, typeTok.line, typeTok.col);
504
504
  }
505
505
  const name = this.consume(TokenKind.Identifier, "Expected name").lexeme;
506
506
  this.consume(TokenKind.From, "Expected 'from \"<path>\"' after 'raw string <Name>'");
@@ -531,10 +531,10 @@ export class Parser {
531
531
  if (this.check(TokenKind.Constructor)) {
532
532
  const ctorTok = this.advance();
533
533
  if (virtualTok) {
534
- this.diagnostics.error(`'virtual' is not valid on an extern constructor`, virtualTok.line, virtualTok.col);
534
+ this.diagnostics.error("KS2015", `'virtual' is not valid on an extern constructor`, virtualTok.line, virtualTok.col);
535
535
  }
536
536
  if (hasConstructor) {
537
- this.diagnostics.error(`Extern class '${name}' already has a constructor signature`, ctorTok.line, ctorTok.col);
537
+ this.diagnostics.error("KS2016", `Extern class '${name}' already has a constructor signature`, ctorTok.line, ctorTok.col);
538
538
  }
539
539
  ctorParams = this.parseParamList();
540
540
  hasConstructor = true;
@@ -550,7 +550,7 @@ export class Parser {
550
550
  }
551
551
  else if (this.check(TokenKind.LBrace)) {
552
552
  if (virtualTok) {
553
- this.diagnostics.error(`'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
553
+ this.diagnostics.error("KS2017", `'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
554
554
  }
555
555
  this.advance();
556
556
  this.consume(TokenKind.Get, "Expected 'get' in extern property accessor list");
@@ -566,7 +566,7 @@ export class Parser {
566
566
  }
567
567
  else {
568
568
  if (virtualTok) {
569
- this.diagnostics.error(`'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
569
+ this.diagnostics.error("KS2018", `'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
570
570
  }
571
571
  this.consume(TokenKind.Semicolon, "Expected ';' after extern property declaration");
572
572
  properties.push({ isStatic, name: memberName, type, hasSetter: true });
@@ -671,7 +671,7 @@ export class Parser {
671
671
  finallyBlock = this.parseBlock();
672
672
  }
673
673
  if (!catchBlock && !finallyBlock) {
674
- this.diagnostics.error("'try' must be followed by 'catch' and/or 'finally'", start.line, start.col);
674
+ this.diagnostics.error("KS2019", "'try' must be followed by 'catch' and/or 'finally'", start.line, start.col);
675
675
  }
676
676
  return { kind: "TryStatement", tryBlock, catchParam, catchBlock, finallyBlock, line: start.line, col: start.col };
677
677
  }
@@ -945,7 +945,7 @@ export class Parser {
945
945
  this.consume(TokenKind.RParen, "Expected ')' after expression");
946
946
  return expr;
947
947
  }
948
- this.diagnostics.error(`Unexpected token '${t.lexeme || t.kind}'`, t.line, t.col);
948
+ this.diagnostics.error("KS2020", `Unexpected token '${t.lexeme || t.kind}'`, t.line, t.col);
949
949
  throw new ParseError();
950
950
  }
951
951
  // Distinguishes a lambda's parameter list from a parenthesized expression.
@@ -1049,7 +1049,7 @@ export class Parser {
1049
1049
  const subParser = new Parser(subTokens, subDiagnostics);
1050
1050
  const expr = subParser.parseExpression();
1051
1051
  for (const d of subDiagnostics.diagnostics) {
1052
- this.diagnostics.error(d.message, token.line, token.col);
1052
+ this.diagnostics.error("KS2021", d.message, token.line, token.col);
1053
1053
  }
1054
1054
  return expr;
1055
1055
  }
@@ -1109,7 +1109,7 @@ export class Parser {
1109
1109
  if (this.check(kind))
1110
1110
  return this.advance();
1111
1111
  const t = this.peek();
1112
- this.diagnostics.error(message, t.line, t.col);
1112
+ this.diagnostics.error("KS2022", message, t.line, t.col);
1113
1113
  throw new ParseError();
1114
1114
  }
1115
1115
  synchronize() {
@@ -0,0 +1,113 @@
1
+ // A minimal, self-contained source-map v3 (base64 VLQ) encoder — hand-rolled
2
+ // rather than pulling in the `source-map` package, to keep kopscript at zero
3
+ // runtime dependencies (see README.md/package.json). No imports beyond this
4
+ // file's own code; nothing here reads or writes the filesystem.
5
+ //
6
+ // Spec: https://sourcemaps.info/spec.html — the encoding this file
7
+ // implements (VLQ digit format, per-line column reset) is described there.
8
+ const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
+ const VLQ_BASE_SHIFT = 5;
10
+ const VLQ_BASE = 1 << VLQ_BASE_SHIFT; // 32 — 5 data bits per base64 digit
11
+ const VLQ_BASE_MASK = VLQ_BASE - 1; // 0x1F
12
+ const VLQ_CONTINUATION_BIT = VLQ_BASE; // 0x20 — set on every digit but the last
13
+ // Sign-and-magnitude, not two's complement: the sign lives in the low bit of
14
+ // the shifted value, not as a separate leading digit — this is what makes a
15
+ // small negative delta (the common case for a line's *second* segment
16
+ // resetting a column back down) encode just as compactly as a small
17
+ // positive one.
18
+ function toSignedVLQ(value) {
19
+ return value < 0 ? (-value << 1) + 1 : value << 1;
20
+ }
21
+ function encodeVLQValue(value) {
22
+ let vlq = toSignedVLQ(value);
23
+ let result = "";
24
+ do {
25
+ let digit = vlq & VLQ_BASE_MASK;
26
+ vlq >>>= VLQ_BASE_SHIFT;
27
+ if (vlq > 0)
28
+ digit |= VLQ_CONTINUATION_BIT;
29
+ result += BASE64_CHARS[digit];
30
+ } while (vlq > 0);
31
+ return result;
32
+ }
33
+ // Encodes one mapping segment's fields (already converted to deltas by the
34
+ // caller) as a contiguous run of VLQ digit-groups — e.g. `[0, 0, 0, 0]` (the
35
+ // "no change from the previous segment" case) always encodes to `"AAAA"`.
36
+ export function encodeVLQ(values) {
37
+ return values.map(encodeVLQValue).join("");
38
+ }
39
+ // Builds one file's source-map v3 JSON incrementally, one `addMapping` call
40
+ // per source position worth recording (see codegen.ts for how call sites are
41
+ // chosen — statement-level, not full expression/column granularity). Only
42
+ // ever describes a single source file per generated file, matching
43
+ // kopscript's one-.ks-file-in, one-.js-file-out compilation model.
44
+ export class SourceMapBuilder {
45
+ constructor(fileName, sourceFileName, sourceContent) {
46
+ // `mappings` is one comma-joined segment-string per GENERATED line,
47
+ // joined with `;` at the end — an empty array entry for a generated line
48
+ // with no recorded mapping produces the required-but-empty group between
49
+ // two semicolons, not a skipped line (that would shift every later line's
50
+ // meaning, since position in the `;`-separated list *is* the line number).
51
+ this.lineGroups = [];
52
+ // Per the spec, `generatedColumn` deltas reset to being measured from 0
53
+ // at the start of every generated line, but `sourceLine`/`sourceColumn`
54
+ // deltas accumulate across the *entire* file, never resetting — tracking
55
+ // both correctly is the one genuinely easy-to-get-wrong part of this.
56
+ this.prevGeneratedColumn = 0;
57
+ this.prevSourceLine = 0;
58
+ this.prevSourceColumn = 0;
59
+ this.fileName = fileName;
60
+ this.sourceFileName = sourceFileName;
61
+ this.sourceContent = sourceContent;
62
+ }
63
+ lineGroup(generatedLine) {
64
+ const index = generatedLine - 1;
65
+ while (this.lineGroups.length <= index)
66
+ this.lineGroups.push([]);
67
+ return this.lineGroups[index];
68
+ }
69
+ // `generatedLine`/`sourceLine`/`sourceColumn` are 1-indexed (kopscript's
70
+ // own convention throughout the lexer/parser/AST — see lexer.ts). Per the
71
+ // source-map spec itself, everything is 0-indexed in the encoded output;
72
+ // the `- 1`s below are that conversion, done in exactly one place.
73
+ // `generatedColumn` is the real (already 0-indexed) character offset from
74
+ // the start of its generated line — there's no separate convention to
75
+ // convert there since "0 spaces of indent" already means column 0.
76
+ addMapping(generatedLine, generatedColumn, sourceLine, sourceColumn) {
77
+ const group = this.lineGroup(generatedLine);
78
+ if (group.length === 0)
79
+ this.prevGeneratedColumn = 0; // new line: column deltas restart at 0
80
+ const zSourceLine = sourceLine - 1;
81
+ const zSourceColumn = sourceColumn - 1;
82
+ group.push(encodeVLQ([
83
+ generatedColumn - this.prevGeneratedColumn,
84
+ 0, // sourceIndex delta — always 0, exactly one source file per map
85
+ zSourceLine - this.prevSourceLine,
86
+ zSourceColumn - this.prevSourceColumn,
87
+ ]));
88
+ this.prevGeneratedColumn = generatedColumn;
89
+ this.prevSourceLine = zSourceLine;
90
+ this.prevSourceColumn = zSourceColumn;
91
+ }
92
+ // Prepends `count` empty line-groups — for when marks were recorded
93
+ // relative to a chunk's own start (line 1) before it was known how many
94
+ // lines of *other* content would end up preceding that chunk in the final
95
+ // output. See codegen.ts's `generate()`: body statements are marked
96
+ // before it's known whether the `state<T>` prelude will precede them.
97
+ shiftLines(count) {
98
+ if (count <= 0)
99
+ return;
100
+ this.lineGroups.unshift(...Array.from({ length: count }, () => []));
101
+ }
102
+ toJSON() {
103
+ const mappings = this.lineGroups.map((segments) => segments.join(",")).join(";");
104
+ return JSON.stringify({
105
+ version: 3,
106
+ file: this.fileName,
107
+ sources: [this.sourceFileName],
108
+ sourcesContent: [this.sourceContent],
109
+ names: [],
110
+ mappings,
111
+ });
112
+ }
113
+ }
@@ -222,7 +222,7 @@ export class TemplateCompiler {
222
222
  const elementChildren = node.children.filter((c) => c.kind === "element");
223
223
  const textChildren = node.children.filter((c) => c.kind === "text");
224
224
  if (textChildren.length > 0 && elementChildren.length > 0) {
225
- this.diagnostics.error(`<${node.tag}> cannot mix text and element children — Kopular's DOM bindings have no text-node API, so text can only be a whole element's content`, node.line, node.col);
225
+ this.diagnostics.error("KS5015", `<${node.tag}> cannot mix text and element children — Kopular's DOM bindings have no text-node API, so text can only be a whole element's content`, node.line, node.col);
226
226
  }
227
227
  else if (textChildren.length > 0) {
228
228
  const parts = textChildren.flatMap((t) => t.parts).map((p) => (p.kind === "Expr" ? { kind: "Expr", expression: this.resolve(p.expression, localScope) } : p));
@@ -130,7 +130,7 @@ export class TemplateLexer {
130
130
  while (!this.isAtEnd() && this.peek() !== '"')
131
131
  value += this.advance();
132
132
  if (this.isAtEnd()) {
133
- this.diagnostics.error("Unterminated attribute value", line, col);
133
+ this.diagnostics.error("KS5001", "Unterminated attribute value", line, col);
134
134
  }
135
135
  else {
136
136
  this.advance(); // closing '"'
@@ -143,7 +143,7 @@ export class TemplateLexer {
143
143
  while (!this.isAtEnd() && !/[\s=>/]/.test(this.peek()))
144
144
  name += this.advance();
145
145
  if (name.length === 0) {
146
- this.diagnostics.error(`Unexpected character '${this.peek()}' in tag`, line, col);
146
+ this.diagnostics.error("KS5002", `Unexpected character '${this.peek()}' in tag`, line, col);
147
147
  this.advance(); // avoid an infinite loop on a genuinely unexpected character
148
148
  return this.nextTagToken();
149
149
  }
@@ -44,7 +44,7 @@ export class TemplateParser {
44
44
  const expr = new Parser(tokens, localDiagnostics).parseStandaloneExpression();
45
45
  for (const d of localDiagnostics.diagnostics) {
46
46
  const { line, col } = remapPosition(fragmentLine, fragmentCol, d.line, d.col);
47
- this.diagnostics.error(d.message, line, col);
47
+ this.diagnostics.error("KS5003", d.message, line, col);
48
48
  }
49
49
  return expr;
50
50
  }
@@ -57,7 +57,7 @@ export class TemplateParser {
57
57
  const tokens = new Lexer(source, localDiagnostics).tokenize();
58
58
  const ofIndex = tokens.findIndex((t) => t.kind === TokenKind.Identifier && t.lexeme === "of");
59
59
  if (ofIndex < 1 || tokens[ofIndex - 1].kind !== TokenKind.Identifier) {
60
- this.diagnostics.error(`Expected '*for="Type varName of iterable"', got '${source}'`, fragmentLine, fragmentCol);
60
+ this.diagnostics.error("KS5004", `Expected '*for="Type varName of iterable"', got '${source}'`, fragmentLine, fragmentCol);
61
61
  return null;
62
62
  }
63
63
  const varNameToken = tokens[ofIndex - 1];
@@ -67,7 +67,7 @@ export class TemplateParser {
67
67
  const iterable = new Parser(iterableTokens, localDiagnostics).parseStandaloneExpression();
68
68
  for (const d of localDiagnostics.diagnostics) {
69
69
  const { line, col } = remapPosition(fragmentLine, fragmentCol, d.line, d.col);
70
- this.diagnostics.error(d.message, line, col);
70
+ this.diagnostics.error("KS5005", d.message, line, col);
71
71
  }
72
72
  return { varType, varName: varNameToken.lexeme, iterable, line: fragmentLine, col: fragmentCol };
73
73
  }
@@ -104,7 +104,7 @@ export class TemplateParser {
104
104
  const exprCol = col;
105
105
  advancePos(text.slice(i, exprStartOffset));
106
106
  if (end === -1) {
107
- this.diagnostics.error("Unterminated '{{' interpolation", exprLine, exprCol);
107
+ this.diagnostics.error("KS5006", "Unterminated '{{' interpolation", exprLine, exprCol);
108
108
  break;
109
109
  }
110
110
  const exprSource = text.slice(exprStartOffset, end);
@@ -139,7 +139,7 @@ export class TemplateParser {
139
139
  const elementRoots = roots.filter((r) => r.kind === "element");
140
140
  if (elementRoots.length !== 1) {
141
141
  const at = roots[0] ?? { line: 1, col: 1 };
142
- this.diagnostics.error(`A template must have exactly one top-level element, found ${elementRoots.length}`, at.line, at.col);
142
+ this.diagnostics.error("KS5007", `A template must have exactly one top-level element, found ${elementRoots.length}`, at.line, at.col);
143
143
  return null;
144
144
  }
145
145
  return elementRoots[0];
@@ -153,7 +153,7 @@ export class TemplateParser {
153
153
  return this.parseElement();
154
154
  }
155
155
  const t = this.advance();
156
- this.diagnostics.error(`Unexpected token in template ('${t.lexeme}')`, t.line, t.col);
156
+ this.diagnostics.error("KS5008", `Unexpected token in template ('${t.lexeme}')`, t.line, t.col);
157
157
  return null;
158
158
  }
159
159
  parseElement() {
@@ -183,7 +183,7 @@ export class TemplateParser {
183
183
  }
184
184
  else if (name === "*if") {
185
185
  if (ifCondition || forBinding) {
186
- this.diagnostics.error(`Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
186
+ this.diagnostics.error("KS5009", `Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
187
187
  }
188
188
  else {
189
189
  ifCondition = this.parseEmbeddedExpression(valueTok.lexeme, valueTok.line, valueTok.col);
@@ -191,7 +191,7 @@ export class TemplateParser {
191
191
  }
192
192
  else if (name === "*for") {
193
193
  if (ifCondition || forBinding) {
194
- this.diagnostics.error(`Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
194
+ this.diagnostics.error("KS5010", `Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
195
195
  }
196
196
  else {
197
197
  forBinding = this.parseForBinding(valueTok.lexeme, valueTok.line, valueTok.col);
@@ -219,11 +219,11 @@ export class TemplateParser {
219
219
  if (this.check(TemplateTokenKind.TagClose)) {
220
220
  const closeTok = this.advance();
221
221
  if (closeTok.lexeme !== tag) {
222
- this.diagnostics.error(`Mismatched closing tag: expected '</${tag}>', got '</${closeTok.lexeme}>'`, closeTok.line, closeTok.col);
222
+ this.diagnostics.error("KS5011", `Mismatched closing tag: expected '</${tag}>', got '</${closeTok.lexeme}>'`, closeTok.line, closeTok.col);
223
223
  }
224
224
  }
225
225
  else {
226
- this.diagnostics.error(`Expected closing tag '</${tag}>'`, tagTok.line, tagTok.col);
226
+ this.diagnostics.error("KS5012", `Expected closing tag '</${tag}>'`, tagTok.line, tagTok.col);
227
227
  }
228
228
  }
229
229
  return { kind: "element", tag, staticAttrs, propBindings, eventBindings, ifCondition, forBinding, children, line: tagTok.line, col: tagTok.col };
@@ -233,7 +233,7 @@ export class TemplateParser {
233
233
  }
234
234
  consumeAttrValue(attrNameTok) {
235
235
  if (!this.check(TemplateTokenKind.AttrValue)) {
236
- this.diagnostics.error(`Expected a quoted value after '${attrNameTok.lexeme}='`, attrNameTok.line, attrNameTok.col);
236
+ this.diagnostics.error("KS5013", `Expected a quoted value after '${attrNameTok.lexeme}='`, attrNameTok.line, attrNameTok.col);
237
237
  return null;
238
238
  }
239
239
  return this.advance();
@@ -243,7 +243,7 @@ export class TemplateParser {
243
243
  this.advance();
244
244
  }
245
245
  else {
246
- this.diagnostics.error(message, at.line, at.col);
246
+ this.diagnostics.error("KS5014", message, at.line, at.col);
247
247
  }
248
248
  }
249
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopscript",
3
- "version": "0.7.2",
3
+ "version": "0.9.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",