novac 2.0.1 → 2.2.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.
Files changed (161) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1574 -597
  3. package/bin/novac +468 -171
  4. package/bin/nvc +522 -0
  5. package/bin/nvml +78 -17
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +69 -0
  10. package/examples/math.nv +21 -0
  11. package/kits/birdAPI/kitdef.js +954 -0
  12. package/kits/kitRNG/kitdef.js +740 -0
  13. package/kits/kitSSH/kitdef.js +1272 -0
  14. package/kits/kitadb/kitdef.js +606 -0
  15. package/kits/kitai/kitdef.js +2185 -0
  16. package/kits/kitansi/kitdef.js +1402 -0
  17. package/kits/kitcanvas/kitdef.js +914 -0
  18. package/kits/kitclippy/kitdef.js +925 -0
  19. package/kits/kitformat/kitdef.js +1485 -0
  20. package/kits/kitgps/kitdef.js +1862 -0
  21. package/kits/kitlibproc/kitdef.js +3 -2
  22. package/kits/kitmatrix/ex.js +19 -0
  23. package/kits/kitmatrix/kitdef.js +960 -0
  24. package/kits/kitmorse/kitdef.js +229 -0
  25. package/kits/kitmpatch/kitdef.js +906 -0
  26. package/kits/kitnet/kitdef.js +1401 -0
  27. package/kits/kitnovacweb/README.md +1416 -143
  28. package/kits/kitnovacweb/kitdef.js +92 -2
  29. package/kits/kitnovacweb/nvml/executor.js +578 -176
  30. package/kits/kitnovacweb/nvml/index.js +2 -2
  31. package/kits/kitnovacweb/nvml/lexer.js +72 -69
  32. package/kits/kitnovacweb/nvml/parser.js +328 -159
  33. package/kits/kitnovacweb/nvml/renderer.js +770 -270
  34. package/kits/kitparse/kitdef.js +1688 -0
  35. package/kits/kitproto/kitdef.js +613 -0
  36. package/kits/kitqr/kitdef.js +637 -0
  37. package/kits/kitregex++/kitdef.js +1353 -0
  38. package/kits/kitrequire/kitdef.js +1599 -0
  39. package/kits/kitx11/kitdef.js +1 -0
  40. package/kits/kitx11/kitx11.js +2472 -0
  41. package/kits/kitx11/kitx11_conn.js +948 -0
  42. package/kits/kitx11/kitx11_worker.js +121 -0
  43. package/kits/libtea/kitdef.js +2691 -0
  44. package/kits/libterm/ex.js +285 -0
  45. package/kits/libterm/kitdef.js +1927 -0
  46. package/novac/LICENSE +21 -0
  47. package/novac/README.md +1823 -0
  48. package/novac/bin/novac +950 -0
  49. package/novac/bin/nvc +522 -0
  50. package/novac/bin/nvml +542 -0
  51. package/novac/demo.nv +245 -0
  52. package/novac/demo_builtins.nv +209 -0
  53. package/novac/demo_http.nv +62 -0
  54. package/novac/examples/bf.nv +69 -0
  55. package/novac/examples/math.nv +21 -0
  56. package/novac/kits/kitai/kitdef.js +2185 -0
  57. package/novac/kits/kitansi/kitdef.js +1402 -0
  58. package/novac/kits/kitformat/kitdef.js +1485 -0
  59. package/novac/kits/kitgps/kitdef.js +1862 -0
  60. package/novac/kits/kitlibfs/kitdef.js +231 -0
  61. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  62. package/novac/kits/kitmatrix/ex.js +19 -0
  63. package/novac/kits/kitmatrix/kitdef.js +960 -0
  64. package/novac/kits/kitmpatch/kitdef.js +906 -0
  65. package/novac/kits/kitnovacweb/README.md +1572 -0
  66. package/novac/kits/kitnovacweb/demo.nv +12 -0
  67. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  68. package/novac/kits/kitnovacweb/index.nova +12 -0
  69. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  70. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  71. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  72. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  73. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  74. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  75. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  76. package/novac/kits/kitparse/kitdef.js +1688 -0
  77. package/novac/kits/kitregex++/kitdef.js +1353 -0
  78. package/novac/kits/kitrequire/kitdef.js +1599 -0
  79. package/novac/kits/kitx11/kitdef.js +1 -0
  80. package/novac/kits/kitx11/kitx11.js +2472 -0
  81. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  82. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  83. package/novac/kits/libtea/tf.js +2691 -0
  84. package/novac/kits/libterm/ex.js +285 -0
  85. package/novac/kits/libterm/kitdef.js +1927 -0
  86. package/novac/node_modules/chalk/license +9 -0
  87. package/novac/node_modules/chalk/package.json +83 -0
  88. package/novac/node_modules/chalk/readme.md +297 -0
  89. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  90. package/novac/node_modules/chalk/source/index.js +225 -0
  91. package/novac/node_modules/chalk/source/utilities.js +33 -0
  92. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  93. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  94. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  95. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  96. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  97. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  98. package/novac/node_modules/commander/LICENSE +22 -0
  99. package/novac/node_modules/commander/Readme.md +1176 -0
  100. package/novac/node_modules/commander/esm.mjs +16 -0
  101. package/novac/node_modules/commander/index.js +24 -0
  102. package/novac/node_modules/commander/lib/argument.js +150 -0
  103. package/novac/node_modules/commander/lib/command.js +2777 -0
  104. package/novac/node_modules/commander/lib/error.js +39 -0
  105. package/novac/node_modules/commander/lib/help.js +747 -0
  106. package/novac/node_modules/commander/lib/option.js +380 -0
  107. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  108. package/novac/node_modules/commander/package-support.json +19 -0
  109. package/novac/node_modules/commander/package.json +82 -0
  110. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  111. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  112. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  113. package/novac/node_modules/node-addon-api/README.md +95 -0
  114. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  115. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  116. package/novac/node_modules/node-addon-api/index.js +14 -0
  117. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  118. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  119. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  120. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  121. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  122. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  123. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  124. package/novac/node_modules/node-addon-api/package.json +480 -0
  125. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  126. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  127. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  128. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  129. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  130. package/novac/node_modules/serialize-javascript/README.md +149 -0
  131. package/novac/node_modules/serialize-javascript/index.js +297 -0
  132. package/novac/node_modules/serialize-javascript/package.json +33 -0
  133. package/novac/package.json +27 -0
  134. package/novac/scripts/update-bin.js +24 -0
  135. package/novac/src/core/bstd.js +1035 -0
  136. package/novac/src/core/config.js +155 -0
  137. package/novac/src/core/describe.js +187 -0
  138. package/novac/src/core/emitter.js +499 -0
  139. package/novac/src/core/error.js +86 -0
  140. package/novac/src/core/executor.js +5606 -0
  141. package/novac/src/core/formatter.js +686 -0
  142. package/novac/src/core/lexer.js +1026 -0
  143. package/novac/src/core/nova_builtins.js +717 -0
  144. package/novac/src/core/nova_thread_worker.js +166 -0
  145. package/novac/src/core/parser.js +2181 -0
  146. package/novac/src/core/types.js +112 -0
  147. package/novac/src/index.js +28 -0
  148. package/novac/src/runtime/stdlib.js +244 -0
  149. package/package.json +6 -3
  150. package/scripts/update-bin.js +0 -0
  151. package/src/core/bstd.js +838 -362
  152. package/src/core/executor.js +2578 -170
  153. package/src/core/lexer.js +502 -54
  154. package/src/core/nova_builtins.js +21 -3
  155. package/src/core/parser.js +413 -72
  156. package/src/core/types.js +30 -2
  157. package/src/index.js +0 -0
  158. package/examples/example-project/README.md +0 -3
  159. package/examples/example-project/src/main.nova +0 -3
  160. package/src/core/environment.js +0 -0
  161. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -10,7 +10,7 @@
10
10
  * @decorator syntax (stored but not enforced)
11
11
  * 'new' as expression keyword -> call with new flag
12
12
  */
13
- const { Lexer, PUNCTUATION } = require('./lexer');
13
+ const { Lexer, CLASSIC_KEYWORDS, PUNCTUATION } = require('./lexer');
14
14
  const { CustomError } = require('./error');
15
15
 
16
16
  class Parser {
@@ -19,6 +19,7 @@ class Parser {
19
19
  this.rawsrc = source;
20
20
  this.current = 0;
21
21
  this.symbols = new Map();
22
+ this.classicMode = false; // true while parsing inside @classic { }
22
23
  }
23
24
 
24
25
  parse() {
@@ -37,7 +38,19 @@ class Parser {
37
38
  statement() {
38
39
  // decorators
39
40
  let decorators = [];
40
- while (this.check('PUNCTUATION', '@')) { this.advance(); decorators.push(this.expression()); }
41
+ while (this.check('PUNCTUATION', '@')) {
42
+ // peek if this is @classic — if so, parse as classic block not decorator
43
+ const savedCurrent = this.current;
44
+ this.advance(); // consume @
45
+ if (this.check('KEYWORD', 'classic') || (this.check('IDENTIFIER', 'classic'))) {
46
+ this.advance(); // consume 'classic'
47
+ return this.classicBlock();
48
+ }
49
+ // not @classic — restore and treat as decorator
50
+ this.current = savedCurrent;
51
+ this.advance();
52
+ decorators.push(this.expression());
53
+ }
41
54
 
42
55
  if (this.match('KEYWORD', 'class')) return this.classDeclaration(decorators);
43
56
  if (this.match('KEYWORD', 'type')) return this.typeDeclaration();
@@ -72,59 +85,59 @@ class Parser {
72
85
  if (this.match('KEYWORD', 'emit')) return this.emitStatement();
73
86
  if (this.match('KEYWORD', 'on')) return this.onStatement();
74
87
  if (this.match('KEYWORD', 'where')) return this.whereStatement();
75
- // ── Nova Classic features ──
76
- if (this.match('KEYWORD', 'foreach')) return this.foreachStatement();
77
- if (this.match('KEYWORD', 'temp')) return this.tempStatement();
78
- if (this.match('KEYWORD', 'keep')) return this.keepStatement();
79
- if (this.match('KEYWORD', 'echo')) return this.echoStatement();
80
- if (this.match('KEYWORD', 'gear')) return this.gearStatement();
81
- if (this.match('KEYWORD', 'engage')) return this.engageStatement();
82
- if (this.match('KEYWORD', 'sandbox')) return this.sandboxStatement();
83
- if (this.match('KEYWORD', 'infer')) return this.inferStatement();
84
- if (this.match('KEYWORD', 'addto')) return this.addtoStatement();
85
- if (this.match('KEYWORD', 'macro')) return this.macroStatement();
86
- if (this.match('KEYWORD', 'block')) return this.blockDeclStatement();
87
- if (this.match('KEYWORD', 'snippet')) return this.snippetStatement();
88
- if (this.match('KEYWORD', 'defunc')) return this.defuncStatement();
89
- if (this.match('KEYWORD', 'lambda')) return this.lambdaStatement();
90
- if (this.match('KEYWORD', 'compose')) return this.composeStatement();
91
- if (this.match('KEYWORD', 'partial')) return this.partialStatement();
92
- if (this.match('KEYWORD', 'backup')) return this.backupStatement();
93
- if (this.match('KEYWORD', 'retrieve')) return this.retrieveStatement();
94
- if (this.match('KEYWORD', 'describe')) return this.describeStatement();
95
- if (this.match('KEYWORD', 'using')) return this.usingStatement();
96
- if (this.match('KEYWORD', 'unuse')) return this.unuseStatement();
97
- if (this.match('KEYWORD', 'classify')) return this.classifyStatement();
98
- if (this.match('KEYWORD', 'rate')) return this.rateStatement();
99
- if (this.check('EXEC_COMMENT')) { const t = this.advance(); return this.nd({ kind: 'exec_comment', code: t.value }); }
100
- // ── Nova Classic (ported from old impl) ──
101
88
  if (this.match('KEYWORD', 'guard')) return this.guardStatement();
102
89
  if (this.match('KEYWORD', 'when')) return this.whenStatement();
103
90
  if (this.match('KEYWORD', 'with')) return this.withStatement();
104
91
  if (this.match('KEYWORD', 'loop')) return this.loopStatement();
105
92
  if (this.match('KEYWORD', 'wait')) return this.waitStatement();
106
- if (this.match('KEYWORD', 'lend')) return this.lendStatement();
107
- if (this.match('KEYWORD', 'sstream')) return this.sstreamStatement();
108
- if (this.match('KEYWORD', 'declare')) return this.declareStatement();
109
- if (this.match('KEYWORD', 'session')) return this.sessionStatement();
110
- if (this.match('KEYWORD', 'enter')) return this.enterStatement();
111
- if (this.match('KEYWORD', 'resu')) return this.resuStatement();
112
- if (this.match('KEYWORD', 'warn')) return this.warnStatement();
113
- if (this.match('KEYWORD', 'info')) return this.infoStatement();
114
- if (this.match('KEYWORD', 'skip')) { this.match('PUNCTUATION', ';'); return this.nd({ kind: 'skip' }); }
115
- if (this.match('KEYWORD', 'end')) { this.match('PUNCTUATION', ';'); return this.nd({ kind: 'end_stmt' }); }
116
- if (this.match('KEYWORD', 'clear')) { this.match('PUNCTUATION', ';'); return this.nd({ kind: 'clear_env' }); }
117
- if (this.match('KEYWORD', 'time')) return this.timeStatement();
118
- if (this.match('KEYWORD', 'keyfunc')) return this.keyfuncStatement();
119
- if (this.match('KEYWORD', 'print')) return this.printStatement();
120
- if (this.match('KEYWORD', 'log')) return this.logStatement();
121
- if (this.match('KEYWORD', 'println')) return this.printlnStatement();
122
- if (this.match('KEYWORD', 'logln')) return this.loglnStatement();
123
- if (this.match('KEYWORD', 'expt')) return this.exptStatement();
124
- if (this.match('KEYWORD', 'option')) return this.optionStatement();
125
- if (this.match('KEYWORD', 'env')) return this.envStatement();
126
- if (this.match('KEYWORD', 'hello')) return this.helloStatement();
127
- if (this.match('KEYWORD', 'eval')) return this.evalStatement();
93
+ if (this.check('EXEC_COMMENT')) { const t = this.advance(); return this.nd({ kind: 'exec_comment', code: t.value }); }
94
+ // ── Nova Classic features — only valid inside @classic { } ──
95
+ if (this.match('KEYWORD', 'foreach')) return this.classicMode ? this.foreachStatement() : this._classicError('foreach');
96
+ if (this.match('KEYWORD', 'temp')) return this.classicMode ? this.tempStatement() : this._classicError('temp');
97
+ if (this.match('KEYWORD', 'keep')) return this.classicMode ? this.keepStatement() : this._classicError('keep');
98
+ if (this.match('KEYWORD', 'echo')) return this.classicMode ? this.echoStatement() : this._classicError('echo');
99
+ if (this.match('KEYWORD', 'gear')) return this.classicMode ? this.gearStatement() : this._classicError('gear');
100
+ if (this.match('KEYWORD', 'engage')) return this.classicMode ? this.engageStatement() : this._classicError('engage');
101
+ if (this.match('KEYWORD', 'sandbox')) return this.classicMode ? this.sandboxStatement() : this._classicError('sandbox');
102
+ if (this.match('KEYWORD', 'infer')) return this.classicMode ? this.inferStatement() : this._classicError('infer');
103
+ if (this.match('KEYWORD', 'addto')) return this.classicMode ? this.addtoStatement() : this._classicError('addto');
104
+ if (this.match('KEYWORD', 'macro')) return this.classicMode ? this.macroStatement() : this._classicError('macro');
105
+ if (this.match('KEYWORD', 'block')) return this.blockDeclStatement();
106
+ if (this.match('KEYWORD', 'snippet')) return this.classicMode ? this.snippetStatement() : this._classicError('snippet');
107
+ if (this.match('KEYWORD', 'defunc')) return this.classicMode ? this.defuncStatement() : this._classicError('defunc');
108
+ if (this.match('KEYWORD', 'lambda')) return this.classicMode ? this.lambdaStatement() : this._classicError('lambda');
109
+ if (this.match('KEYWORD', 'compose')) return this.classicMode ? this.composeStatement() : this._classicError('compose');
110
+ if (this.match('KEYWORD', 'partial')) return this.classicMode ? this.partialStatement() : this._classicError('partial');
111
+ if (this.match('KEYWORD', 'backup')) return this.classicMode ? this.backupStatement() : this._classicError('backup');
112
+ if (this.match('KEYWORD', 'retrieve')) return this.classicMode ? this.retrieveStatement() : this._classicError('retrieve');
113
+ if (this.match('KEYWORD', 'describe')) return this.classicMode ? this.describeStatement() : this._classicError('describe');
114
+ if (this.match('KEYWORD', 'using')) return this.classicMode ? this.usingStatement() : this._classicError('using');
115
+ if (this.match('KEYWORD', 'unuse')) return this.classicMode ? this.unuseStatement() : this._classicError('unuse');
116
+ if (this.match('KEYWORD', 'classify')) return this.classicMode ? this.classifyStatement() : this._classicError('classify');
117
+ if (this.match('KEYWORD', 'rate')) return this.classicMode ? this.rateStatement() : this._classicError('rate');
118
+ if (this.match('KEYWORD', 'lend')) return this.classicMode ? this.lendStatement() : this._classicError('lend');
119
+ if (this.match('KEYWORD', 'sstream')) return this.classicMode ? this.sstreamStatement() : this._classicError('sstream');
120
+ if (this.match('KEYWORD', 'declare')) return this.classicMode ? this.declareStatement() : this._classicError('declare');
121
+ if (this.match('KEYWORD', 'session')) return this.classicMode ? this.sessionStatement() : this._classicError('session');
122
+ if (this.match('KEYWORD', 'enter')) return this.classicMode ? this.enterStatement() : this._classicError('enter');
123
+ if (this.match('KEYWORD', 'resu')) return this.classicMode ? this.resuStatement() : this._classicError('resu');
124
+ if (this.match('KEYWORD', 'warn')) return this.classicMode ? this.warnStatement() : this._classicError('warn');
125
+ if (this.match('KEYWORD', 'info')) return this.classicMode ? this.infoStatement() : this._classicError('info');
126
+ if (this.match('KEYWORD', 'skip')) { if (!this.classicMode) return this._classicError('skip'); this.match('PUNCTUATION', ';'); return this.nd({ kind: 'skip' }); }
127
+ if (this.match('KEYWORD', 'end')) { if (!this.classicMode) return this._classicError('end'); this.match('PUNCTUATION', ';'); return this.nd({ kind: 'end_stmt' }); }
128
+ if (this.match('KEYWORD', 'clear')) { if (!this.classicMode) return this._classicError('clear'); this.match('PUNCTUATION', ';'); return this.nd({ kind: 'clear_env' }); }
129
+ if (this.match('KEYWORD', 'time')) return this.classicMode ? this.timeStatement() : this._classicError('time');
130
+ if (this.match('KEYWORD', 'keyfunc')) return this.classicMode ? this.keyfuncStatement() : this._classicError('keyfunc');
131
+ if (this.match('KEYWORD', 'print')) return this.classicMode ? this.printStatement() : this._classicError('print');
132
+ if (this.match('KEYWORD', 'log')) return this.classicMode ? this.logStatement() : this._classicError('log');
133
+ if (this.match('KEYWORD', 'println')) return this.classicMode ? this.printlnStatement() : this._classicError('println');
134
+ if (this.match('KEYWORD', 'logln')) return this.classicMode ? this.loglnStatement() : this._classicError('logln');
135
+ if (this.match('KEYWORD', 'expt')) return this.classicMode ? this.exptStatement() : this._classicError('expt');
136
+ if (this.match('KEYWORD', 'option')) return this.classicMode ? this.optionStatement() : this._classicError('option');
137
+ if (this.match('KEYWORD', 'env')) return this.classicMode ? this.envStatement() : this._classicError('env');
138
+ if (this.match('KEYWORD', 'hello')) return this.classicMode ? this.helloStatement() : this._classicError('hello');
139
+ if (this.match('KEYWORD', 'eval')) return this.classicMode ? this.evalStatement() : this._classicError('eval');
140
+ if (this.match('KEYWORD', 'register_escape')) return this.classicMode ? this.registerEscapeStatement() : this._classicError('register_escape');
128
141
  if (this.match('KEYWORD', 'import')) return this.importStatement();
129
142
  if (this.match('KEYWORD', 'import_builtin')) return this.importBuiltinStatement();
130
143
  if (this.match('KEYWORD', 'from')) return this.fromStatement();
@@ -134,9 +147,89 @@ class Parser {
134
147
  if (this.match('KEYWORD', 'default')) return this.defaultStatement();
135
148
  if (this.match('KEYWORD', 'namespace')) return this.namespaceStatement();
136
149
  if (this.match('PUNCTUATION', '{')) return this.blockBody(true);
150
+ if (this.match('PUNCTUATION', '.')) return this.dotCmd();
137
151
  return this.expressionStatement();
138
152
  }
139
153
 
154
+ // ── @classic { } block ──
155
+ // Re-tokenizes the raw source of the block body using the lexer in classicMode,
156
+ // then parses all the statements inside with classicMode = true.
157
+ classicBlock() {
158
+ this.consume('PUNCTUATION', '{', 'Expected { after @classic');
159
+ // Collect raw source of the block body by scanning the original source
160
+ // from the current token's position to the matching '}'
161
+ const nodes = [];
162
+ const prevClassic = this.classicMode;
163
+ this.classicMode = true;
164
+ // Re-inject classic keywords: re-tokenize remaining source in classic mode
165
+ // then splice into this.tokens from current position onward.
166
+ const startPos = this.current;
167
+ // Find the raw source span for the block body
168
+ const startToken = this.tokens[startPos];
169
+ // Walk tokens to find matching } depth
170
+ let depth = 1;
171
+ let endPos = startPos;
172
+ while (endPos < this.tokens.length && depth > 0) {
173
+ const t = this.tokens[endPos];
174
+ if (t.type === 'PUNCTUATION' && t.value === '{') depth++;
175
+ if (t.type === 'PUNCTUATION' && t.value === '}') depth--;
176
+ if (depth > 0) endPos++;
177
+ }
178
+ // Extract raw source substring for the block body
179
+ const srcStart = startToken ? startToken.column : 0;
180
+ const endToken = this.tokens[endPos];
181
+ // Use line/column info to re-lex just the block body in classic mode
182
+ // Simpler: re-lex from the raw source, find the block span by matching braces
183
+ const rawBody = this._extractBlockSource();
184
+ const classicLexer = new Lexer(rawBody, true);
185
+ classicLexer.definitions = new Map(); // fresh definitions
186
+ const classicTokens = classicLexer.tokenize();
187
+ // Replace our remaining tokens (up to and including the closing }) with classic-retokenized ones
188
+ this.tokens.splice(this.current, endPos - this.current + 1, ...classicTokens.slice(0, -1)); // drop EOF
189
+ // Now parse statements until we hit the matching }
190
+ while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
191
+ const n = this.statement();
192
+ if (n) nodes.push(n);
193
+ }
194
+ this.classicMode = prevClassic;
195
+ return this.nd({ kind: 'classic_block', body: nodes });
196
+ }
197
+
198
+ // Extract the raw source text of the { } block at the current position
199
+ // by scanning this.rawsrc for matching braces from the first { token's position.
200
+ _extractBlockSource() {
201
+ // Find character offset of the next { in rawsrc starting near current token's line/col
202
+ const firstTok = this.tokens[this.current];
203
+ // Walk rawsrc to find the opening { that corresponds to current token position
204
+ // Use line tracking to approximate position
205
+ let pos = 0, line = 1;
206
+ while (pos < this.rawsrc.length) {
207
+ if (line === firstTok.line || (firstTok.line <= 1)) {
208
+ // scan forward for {
209
+ const braceIdx = this.rawsrc.indexOf('{', pos);
210
+ if (braceIdx === -1) break;
211
+ // Extract body between matching braces
212
+ let depth = 0, i = braceIdx;
213
+ while (i < this.rawsrc.length) {
214
+ if (this.rawsrc[i] === '{') depth++;
215
+ else if (this.rawsrc[i] === '}') { depth--; if (depth === 0) { return this.rawsrc.slice(braceIdx + 1, i); } }
216
+ i++;
217
+ }
218
+ break;
219
+ }
220
+ if (this.rawsrc[pos] === '\n') line++;
221
+ pos++;
222
+ }
223
+ return '';
224
+ }
225
+
226
+ _classicError(keyword) {
227
+ throw new (require('./error').CustomError('ClassicError'))(
228
+ `'${keyword}' is a Classic novac keyword and must be used inside an @classic { } block.\n` +
229
+ `Wrap your legacy code: @classic { ${keyword} ... }`
230
+ );
231
+ }
232
+
140
233
  // ── type declarations ──
141
234
 
142
235
  typeDeclaration() {
@@ -162,6 +255,7 @@ class Parser {
162
255
 
163
256
  parseTypeExpr() {
164
257
  // Parse: TypeA | TypeB | TypeC or TypeA & TypeB or { field: Type } or Name or Name<T>
258
+ // Also handles literal value unions: 1 | "a" | true | someType
165
259
  if (this.check('PUNCTUATION', '{')) {
166
260
  // object type shape
167
261
  this.advance();
@@ -178,27 +272,52 @@ class Parser {
178
272
  this.consume('PUNCTUATION', '}', 'Expected }');
179
273
  return { kind: 'shape_type', fields };
180
274
  }
181
- // array type: Type[]
182
- let base;
183
- if (this.check('PUNCTUATION', '[') && false) { /* skip */ }
184
- const name = this.consume('IDENTIFIER', 'Expected type name').value;
185
- const args = this.parseTypeParams(); // generic args
186
- base = { kind: 'named_type', name, args };
187
- // [] suffix for array type
188
- while (this.check('PUNCTUATION', '[') && this.next()?.value === ']') {
189
- this.advance(); this.advance();
190
- base = { kind: 'array_type', of: base };
191
- }
275
+
276
+ // Helper: parse a single type atom (identifier or literal)
277
+ const parseAtom = () => {
278
+ // Literal value atoms: number, string, bool keywords
279
+ if (this.check('NUMBER')) {
280
+ const v = this.advance().value;
281
+ return { kind: 'value_type', value: v };
282
+ }
283
+ if (this.check('STRING')) {
284
+ const v = this.advance().value;
285
+ return { kind: 'value_type', value: v };
286
+ }
287
+ if (this.check('LITERAL')) {
288
+ const v = this.advance().value;
289
+ return { kind: 'value_type', value: v };
290
+ }
291
+ // {string}, {bool}, {number} — type-in-braces shorthand
292
+ if (this.check('PUNCTUATION', '{')) {
293
+ this.advance();
294
+ const name = this.consume('IDENTIFIER', 'Expected type name').value;
295
+ this.consume('PUNCTUATION', '}', 'Expected }');
296
+ return { kind: 'named_type', name, args: [] };
297
+ }
298
+ const name = this.consume('IDENTIFIER', 'Expected type name').value;
299
+ const args = this.parseTypeParams();
300
+ let base = { kind: 'named_type', name, args };
301
+ // [] suffix for array type
302
+ while (this.check('PUNCTUATION', '[') && this.next()?.value === ']') {
303
+ this.advance(); this.advance();
304
+ base = { kind: 'array_type', of: base };
305
+ }
306
+ return base;
307
+ };
308
+
309
+ let base = parseAtom();
310
+
192
311
  // union: |
193
312
  if (this.check('OPERATOR', '|')) {
194
313
  const variants = [base];
195
- while (this.match('OPERATOR', '|')) variants.push(this.parseTypeExpr());
314
+ while (this.match('OPERATOR', '|')) variants.push(parseAtom());
196
315
  return { kind: 'union_type', variants };
197
316
  }
198
317
  // intersection: &
199
318
  if (this.check('OPERATOR', '&')) {
200
319
  const parts = [base];
201
- while (this.match('OPERATOR', '&')) parts.push(this.parseTypeExpr());
320
+ while (this.match('OPERATOR', '&')) parts.push(parseAtom());
202
321
  return { kind: 'intersect_type', parts };
203
322
  }
204
323
  return base;
@@ -417,6 +536,16 @@ class Parser {
417
536
  return this.nd({ kind: 'eval', code });
418
537
  }
419
538
 
539
+ // register_escape NAME expr
540
+ // NAME — escape name (identifier)
541
+ // expr — expression whose value becomes the replacement (string or function)
542
+ registerEscapeStatement() {
543
+ const name = this.consume('IDENTIFIER', 'Expected escape name after register_escape').value;
544
+ const value = this.expression();
545
+ this.match('PUNCTUATION', ';');
546
+ return this.nd({ kind: 'register_escape', name, value });
547
+ }
548
+
420
549
  importStatement() {
421
550
  const source = this.expression();
422
551
  let names = null;
@@ -581,7 +710,41 @@ class Parser {
581
710
  } else {
582
711
  nameNode = this.consume('IDENTIFIER', 'Expected variable name');
583
712
  let explicitType = null;
584
- if (this.match('PUNCTUATION', ':')) explicitType = this.parseTypeExpr();
713
+ // ── smart modifiers ──
714
+ const modifiers = {};
715
+ while (true) {
716
+ if (this.match('KEYWORD', 'setter')) {
717
+ // setter <expr> — expression that returns a function (v, old) => newVal
718
+ modifiers.setter = this.expression(15); // high prec so we only grab one token/call
719
+ } else if (this.match('KEYWORD', 'getter')) {
720
+ // getter <expr>
721
+ modifiers.getter = this.expression(15);
722
+ } else if (this.match('KEYWORD', 'frozen')) {
723
+ modifiers.frozen = true;
724
+ } else if (this.match('KEYWORD', 'lazy')) {
725
+ modifiers.lazy = true;
726
+ } else if (this.match('KEYWORD', 'tracked')) {
727
+ modifiers.tracked = true;
728
+ } else if (this.match('KEYWORD', 'nonull')) {
729
+ modifiers.nonull = true;
730
+ } else if (this.match('KEYWORD', 'once')) {
731
+ modifiers.once = true;
732
+ } else if (this.match('KEYWORD', 'as')) {
733
+ // as fnum <expr> | as fint <expr>
734
+ if (this.match('KEYWORD', 'fnum') || this.match('KEYWORD', 'fint')) {
735
+ modifiers.clampType = this.previous().value; // 'fnum' or 'fint'
736
+ modifiers.clampExpr = this.expression(15); // expr returning [min, max]
737
+ } else {
738
+ // fallback: treat as regular type cast — re-read type
739
+ const type = this.parseTypeExpr();
740
+ explicitType = type;
741
+ }
742
+ } else {
743
+ break;
744
+ }
745
+ }
746
+ if (Object.keys(modifiers).length > 0) nameNode = { ...nameNode, modifiers };
747
+ if (!explicitType && this.match('PUNCTUATION', ':')) explicitType = this.parseTypeExpr();
585
748
  nameNode = { ...nameNode, typeAnnotation: explicitType };
586
749
  }
587
750
  let value = null;
@@ -589,7 +752,9 @@ class Parser {
589
752
  this.match('PUNCTUATION', ';');
590
753
  if (destructure) return this.nd({ kind: 'declare', destructure, value, isConst, isPointer });
591
754
  if (nameNode.value) this.symbols.set(nameNode.value, { type: nameNode.typeAnnotation || null, isConst });
592
- return this.nd({ kind: 'declare', name: nameNode.value, value, isConst, isPointer, explicitType: nameNode.typeAnnotation || null });
755
+ return this.nd({ kind: 'declare', name: nameNode.value, value, isConst, isPointer,
756
+ explicitType: nameNode.typeAnnotation || null,
757
+ modifiers: nameNode.modifiers || null });
593
758
  }
594
759
 
595
760
  objectPattern() {
@@ -735,13 +900,68 @@ class Parser {
735
900
  this.consume('PUNCTUATION', ')', 'Expected )');
736
901
  left = this.nd({ kind: 'fetch_expr', url: fUrl, options: fOpts });
737
902
 
903
+ } else if (
904
+ (this.check('KEYWORD', 'async') && this._peekAsyncIsLambda()) ||
905
+ (this.check('IDENTIFIER', 'generator') && this._peekIsLambda())
906
+ ) {
907
+ // ── async/generator lambda expressions ──────────────────────────────
908
+ // Supported forms:
909
+ // async (a, b) => body
910
+ // async v => body
911
+ // generator (a, b) => body
912
+ // generator v => body
913
+ // generator async (a, b) => body (mix)
914
+ // async generator (a, b) => body (mix, generator is an identifier)
915
+ let isAsync = false, isGenerator = false;
916
+ // Consume modifier keywords — allow both orderings.
917
+ // `async` is a KEYWORD; `generator` is an IDENTIFIER in the lexer.
918
+ let consumed = true;
919
+ while (consumed) {
920
+ consumed = false;
921
+ if (this.match('KEYWORD', 'async')) { isAsync = true; consumed = true; }
922
+ if (this.match('IDENTIFIER', 'generator')) { isGenerator = true; consumed = true; }
923
+ }
924
+ // Now expect either `(args)` or a single identifier, followed by `=>`
925
+ let arrowArgs;
926
+ if (this.check('PUNCTUATION', '(')) {
927
+ this.advance();
928
+ const rawArgs = [];
929
+ if (!this.check('PUNCTUATION', ')')) {
930
+ // Parse simple identifier list (no full expressions — avoids comma ambiguity)
931
+ do {
932
+ const t = this.peek();
933
+ if (t.type === 'IDENTIFIER') { this.advance(); rawArgs.push(t.value); }
934
+ else rawArgs.push(this.expression());
935
+ } while (this.match('PUNCTUATION', ','));
936
+ }
937
+ this.consume('PUNCTUATION', ')', 'Expected ) in async/generator lambda');
938
+ arrowArgs = rawArgs.map(a => typeof a === 'string' ? a : (a?.name ?? a));
939
+ } else {
940
+ // single bare identifier
941
+ const id = this.consume('IDENTIFIER', 'Expected argument or ( after async/generator');
942
+ arrowArgs = [id.value];
943
+ }
944
+ this.consume('OPERATOR', '=>', 'Expected => in async/generator lambda');
945
+ const arrowBody = this.check('PUNCTUATION', '{')
946
+ ? this.blockBody()
947
+ : [this.nd({ kind: 'return', value: this.expression(), terminate: true })];
948
+ left = this.nd({ kind: 'arrowfunc', args: arrowArgs, body: arrowBody, isAsync, isGenerator });
949
+
738
950
  } else if (this.check('KEYWORD', 'await')) {
739
951
  this.advance(); left = this.nd({ kind: 'await', operand: this.expression(14) });
740
-
741
952
  } else if (this.check('KEYWORD', 'new')) {
742
953
  this.advance();
743
954
  left = this.nd({ kind: 'new_expr', callee: this.expression(16) });
744
-
955
+ } else if (this.check('OPERATOR', '$')) {
956
+ this.advance();
957
+ const code = this.expression();
958
+ left = this.nd({ kind: 'dexpr', code });
959
+ } else if (this.check('KEYWORD', 'link')) {
960
+ this.advance();
961
+ this.consume('PUNCTUATION', '(', 'Expected ( after link');
962
+ const id = this.expression();
963
+ this.consume('PUNCTUATION', ')', 'Expected )');
964
+ left = this.nd({ kind: 'link', linked: id });
745
965
  } else if (this.check('KEYWORD', 'rate')) {
746
966
  this.advance();
747
967
  this.consume('PUNCTUATION', '(', 'Expected ( after rate');
@@ -749,7 +969,6 @@ class Parser {
749
969
  this.consume('PUNCTUATION', ')', 'Expected )');
750
970
  const rateCastType = this.consumeName('Expected cast type after rate(...)').value;
751
971
  left = this.nd({ kind: 'rate_cast', value: rateVal, cast_type: rateCastType });
752
-
753
972
  } else if (this.check('KEYWORD', 'classify')) {
754
973
  this.advance();
755
974
  const classInner = this.expression(16);
@@ -814,6 +1033,13 @@ class Parser {
814
1033
  } else if (tok.type === 'PUNCTUATION' && tok.value === '{') {
815
1034
  left = this.objectLiteral();
816
1035
 
1036
+ } else if (tok.type === 'PUNCTUATION' && tok.value === '\\') {
1037
+ this.advance();
1038
+ let pattern = this.expression();
1039
+ this.consume('PUNCTUATION', '\\', 'Expected \\ in regex literal');
1040
+ let flags = '';
1041
+ if (this.check('IDENTIFIER')) flags = this.advance().value;
1042
+ left = this.nd({ kind: 'regex_literal', pattern, flags });
817
1043
  } else if (tok.type === 'NUMBER' || tok.type === 'STRING' || tok.type === 'LITERAL') {
818
1044
  left = this.nd({ kind: 'value', value: this.advance().value });
819
1045
 
@@ -834,6 +1060,11 @@ class Parser {
834
1060
  }
835
1061
  left = this.nd({ kind: 'ref', name, explicitType: this.symbols.get(name)?.type ?? null });
836
1062
 
1063
+ } else if (tok.type === 'DVAR') {
1064
+ // Dynamic / meta variable — carry source location for __line__, __col__
1065
+ const t = this.advance();
1066
+ left = this.nd({ kind: 'dvar', name: t.value, srcLine: t.srcLine, srcCol: t.srcCol });
1067
+
837
1068
  } else if (tok.type === 'URL') {
838
1069
  left = this.nd({ kind: 'url_literal', value: this.advance().value });
839
1070
 
@@ -860,6 +1091,15 @@ class Parser {
860
1091
  } else if (tok.type === 'EOF') {
861
1092
  return this.nd({ kind: 'EOF' });
862
1093
 
1094
+ } else if (tok.type === 'KEYWORD' && tok.value === 'nat') {
1095
+ this.advance();
1096
+ left = this.nd({ kind: 'native', value: this.expression() })
1097
+ } else if (tok.type === 'KEYWORD' && tok.value === 'run') {
1098
+ this.advance();
1099
+ left = this.nd({ kind: 'run_expr', code: this.statement() })
1100
+ } else if (tok.value === '=') {
1101
+ this.advance();
1102
+ left = this.nd({ kind: 'ast', ast: this.statement() });
863
1103
  } else {
864
1104
  this.error("Unexpected token '" + tok.value + "'");
865
1105
  }
@@ -869,6 +1109,14 @@ class Parser {
869
1109
  tok = this.peek();
870
1110
  if (tok.type === 'PUNCTUATION' && (tok.value === ';' || tok.value === '}')) break;
871
1111
  if (tok.type === 'PUNCTUATION' && tok.value === '{') break; // always a block
1112
+ if (tok.type === 'KEYWORD' && tok.value === 'if') {
1113
+ this.advance();
1114
+ let cond = this.expression();
1115
+ let elseExpr = null;
1116
+ if (this.match('KEYWORD', 'else')) elseExpr = this.expression();
1117
+ left = this.nd({ kind: 'if_ternary', cond, elseExpr, operand: left });
1118
+ }
1119
+
872
1120
 
873
1121
  // Postfix ++ / --
874
1122
  if (tok.type === 'OPERATOR' && (tok.value === '++' || tok.value === '--')) {
@@ -909,8 +1157,16 @@ class Parser {
909
1157
  // Property access
910
1158
  if (tok.type === 'PUNCTUATION' && tok.value === '.') {
911
1159
  this.advance();
1160
+ if (this.match('PUNCTUATION', '(')) {
1161
+ let nameNode = this.nd({
1162
+ kind: 'subscript', object: left, index: this.nd({kind:'value', value: '.expr'})
1163
+ });
1164
+ const args = this.parseCallArgs();
1165
+ left = this.nd({ kind: 'call', name: nameNode, args })
1166
+ } else {
912
1167
  const prop = this.consumeName('Expected property name after .');
913
1168
  left = this.nd({ kind: 'prop', object: left, name: prop.value }); continue;
1169
+ }
914
1170
  }
915
1171
 
916
1172
  // Subscript
@@ -947,7 +1203,7 @@ class Parser {
947
1203
  return left;
948
1204
  }
949
1205
 
950
- parseCallArgs() {
1206
+ parseCallArgs(needsClosing = true) {
951
1207
  // Caller must have consumed '('
952
1208
  const args = [];
953
1209
  if (!this.check('PUNCTUATION', ')')) {
@@ -956,7 +1212,7 @@ class Parser {
956
1212
  else args.push(this.expression());
957
1213
  } while (this.match('PUNCTUATION', ','));
958
1214
  }
959
- this.consume('PUNCTUATION', ')', 'Expected )');
1215
+ if (needsClosing) this.consume('PUNCTUATION', ')', 'Expected )');
960
1216
  return args;
961
1217
  }
962
1218
 
@@ -1013,6 +1269,21 @@ class Parser {
1013
1269
  // method shorthand
1014
1270
  const { args, body } = this.parseFuncBody();
1015
1271
  props.push({ kind: 'prop', key: propName, value: { kind: 'function', name: propName, args, body } });
1272
+ } else if (this.match('PUNCTUATION', '?')) {
1273
+ // Boolean presence syntax: { key? true } or { key? false }
1274
+ // The value MUST be a boolean — true or false
1275
+ const valTok = this.peek();
1276
+ let boolVal;
1277
+ if (this.match('KEYWORD', 'true') || this.match('LITERAL', 'true')) {
1278
+ boolVal = { kind: 'bool', value: true };
1279
+ } else if (this.match('KEYWORD', 'false') || this.match('LITERAL', 'false')) {
1280
+ boolVal = { kind: 'bool', value: false };
1281
+ } else {
1282
+ // Allow an expression that should evaluate to bool — parse and wrap in bool_assert
1283
+ const expr = this.expression();
1284
+ boolVal = { kind: 'bool_assert', expr };
1285
+ }
1286
+ props.push({ kind: 'prop', key: propName, value: boolVal, boolProp: true });
1016
1287
  } else {
1017
1288
  this.consume('PUNCTUATION', ':', 'Expected : after property name');
1018
1289
  const val = this.expression();
@@ -1080,11 +1351,18 @@ class Parser {
1080
1351
  }
1081
1352
 
1082
1353
  blockBody(consumed) {
1083
- if (!consumed) this.consume('PUNCTUATION', '{', 'Expected {');
1354
+ if (consumed || this.match('PUNCTUATION', '{')) {
1084
1355
  const body = [];
1085
1356
  while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) body.push(this.statement());
1086
1357
  this.consume('PUNCTUATION', '}', 'Expected }');
1087
1358
  return body;
1359
+ } else return [this.statement()];
1360
+ }
1361
+
1362
+ dotCmd() {
1363
+ let cmd = this.consumeName('Expected command name.');
1364
+ let args = this.parseCallArgs(false);
1365
+ return this.nd({ kind: 'dot_cmd', cmd, args })
1088
1366
  }
1089
1367
 
1090
1368
  // Accept any identifier-like token as a name
@@ -1810,10 +2088,73 @@ class Parser {
1810
2088
  previous() { return this.tokens[this.current - 1]; }
1811
2089
  next() { return this.tokens[this.current + 1]; }
1812
2090
  isAtEnd() { return this.peek().type === 'EOF'; }
2091
+
2092
+ // Returns true when the current position looks like the start of a lambda
2093
+ // parameter list — used to distinguish `generator(...)` call from
2094
+ // `generator (...) => ...` or `generator id => ...`.
2095
+ // We look ahead past optional whitespace for: `(`, an IDENTIFIER not followed
2096
+ // by `(`, or the `async` keyword — all of which signal a lambda head.
2097
+ // Like _peekIsLambda but for the `async` keyword at current position.
2098
+ // `async` is a lambda modifier when followed by: `(` + eventual `)=>`,
2099
+ // an identifier + `=>`, or the `generator` identifier.
2100
+ _peekAsyncIsLambda() {
2101
+ const n1 = this.tokens[this.current + 1];
2102
+ if (!n1) return false;
2103
+ if (n1.type === 'IDENTIFIER' && n1.value === 'generator') return true;
2104
+ if (n1.type === 'PUNCTUATION' && n1.value === '(') {
2105
+ let depth = 0, i = this.current + 1;
2106
+ while (i < this.tokens.length) {
2107
+ const t = this.tokens[i++];
2108
+ if (t.type === 'PUNCTUATION' && t.value === '(') depth++;
2109
+ else if (t.type === 'PUNCTUATION' && t.value === ')') {
2110
+ depth--;
2111
+ if (depth === 0) break;
2112
+ }
2113
+ }
2114
+ const afterParen = this.tokens[i];
2115
+ return afterParen && afterParen.type === 'OPERATOR' && afterParen.value === '=>';
2116
+ }
2117
+ if (n1.type === 'IDENTIFIER') {
2118
+ const n2 = this.tokens[this.current + 2];
2119
+ return n2 && n2.type === 'OPERATOR' && n2.value === '=>';
2120
+ }
2121
+ return false;
2122
+ }
2123
+
2124
+ _peekIsLambda() {
2125
+ // After `generator`, the next token decides:
2126
+ const n1 = this.tokens[this.current + 1]; // token after `generator`
2127
+ if (!n1) return false;
2128
+ // `generator async ...` or `generator (` — lambda
2129
+ if (n1.type === 'KEYWORD' && n1.value === 'async') return true;
2130
+ if (n1.type === 'PUNCTUATION' && n1.value === '(') {
2131
+ // Could be a call `generator(...)` or a lambda `generator (...) => ...`
2132
+ // Look further: find the matching `)` then check if `=>` follows
2133
+ let depth = 0, i = this.current + 1;
2134
+ while (i < this.tokens.length) {
2135
+ const t = this.tokens[i++];
2136
+ if (t.type === 'PUNCTUATION' && t.value === '(') depth++;
2137
+ else if (t.type === 'PUNCTUATION' && t.value === ')') {
2138
+ depth--;
2139
+ if (depth === 0) break;
2140
+ }
2141
+ }
2142
+ const afterParen = this.tokens[i];
2143
+ return afterParen && afterParen.type === 'OPERATOR' && afterParen.value === '=>';
2144
+ }
2145
+ // `generator identifier =>` — single-arg lambda
2146
+ if (n1.type === 'IDENTIFIER') {
2147
+ const n2 = this.tokens[this.current + 2];
2148
+ return n2 && n2.type === 'OPERATOR' && n2.value === '=>';
2149
+ }
2150
+ return false;
2151
+ }
1813
2152
  advance() { if (!this.isAtEnd()) this.current++; return this.previous(); }
1814
2153
  check(type, value = null) {
1815
2154
  if (this.isAtEnd()) return false;
1816
2155
  const t = this.peek();
2156
+ // Allow DVAR 'default' to match wherever KEYWORD 'default' is expected
2157
+ if (type === 'KEYWORD' && value === 'default' && t.type === 'DVAR' && t.value === 'default') return true;
1817
2158
  return t.type === type && (value === null || t.value === value);
1818
2159
  }
1819
2160
  match(type, value = null) { if (this.check(type, value)) { this.advance(); return true; } return false; }
@@ -1840,4 +2181,4 @@ class Parser {
1840
2181
  }
1841
2182
  }
1842
2183
 
1843
- module.exports = { Parser };
2184
+ module.exports = { Parser };