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
package/src/core/lexer.js CHANGED
@@ -2,8 +2,329 @@
2
2
  * Nova Lexer v2
3
3
  * Removed: backtick template literals
4
4
  * Added: f"..." strings, full operator set, type/struct/interface/enum/trait keywords,
5
- * numeric separators (1_000_000), &&= ||= ??= logical-assign, get/set keywords
5
+ * numeric separators (1_000_000), &&= ||= ??= logical-assign, get/set keywords,
6
+ * multi-char escape sequences, \ESCAPE{arg1,arg2} argument escapes,
7
+ * 40+ built-in escapes including \color{name}, \textType{type}, full ANSI terminal escapes
6
8
  */
9
+
10
+ // ── ANSI Terminal Escape Engine ────────────────────────────────────────────
11
+ // All escape sequences resolve to raw strings at lex time.
12
+ // Custom escapes registered via #register escape NAME pattern
13
+ // or the `register_escape` Classic statement.
14
+
15
+ // Foreground colors (standard 8 + bright 8 = 16)
16
+ const FG = {
17
+ black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m',
18
+ yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m',
19
+ cyan: '\x1b[36m', white: '\x1b[37m',
20
+ // bright
21
+ brightblack: '\x1b[90m', brightred: '\x1b[91m', brightgreen: '\x1b[92m',
22
+ brightyellow: '\x1b[93m', brightblue: '\x1b[94m', brightmagenta: '\x1b[95m',
23
+ brightcyan: '\x1b[96m', brightwhite: '\x1b[97m',
24
+ // aliases
25
+ grey: '\x1b[90m', gray: '\x1b[90m', orange: '\x1b[33m',
26
+ pink: '\x1b[95m', purple: '\x1b[35m', violet: '\x1b[35m',
27
+ lime: '\x1b[92m', teal: '\x1b[36m', navy: '\x1b[34m',
28
+ maroon: '\x1b[31m', olive: '\x1b[33m', aqua: '\x1b[36m',
29
+ silver: '\x1b[37m', gold: '\x1b[33m', indigo: '\x1b[34m',
30
+ coral: '\x1b[91m', salmon: '\x1b[91m', khaki: '\x1b[33m',
31
+ lavender: '\x1b[95m', turquoise: '\x1b[96m', crimson: '\x1b[31m',
32
+ ivory: '\x1b[97m', beige: '\x1b[97m', tan: '\x1b[33m',
33
+ sienna: '\x1b[31m', amber: '\x1b[33m', emerald: '\x1b[32m',
34
+ ruby: '\x1b[31m', sapphire: '\x1b[34m', jade: '\x1b[32m',
35
+ default: '\x1b[39m',
36
+ };
37
+
38
+ // Background colors
39
+ const BG = {
40
+ black: '\x1b[40m', red: '\x1b[41m', green: '\x1b[42m',
41
+ yellow: '\x1b[43m', blue: '\x1b[44m', magenta: '\x1b[45m',
42
+ cyan: '\x1b[46m', white: '\x1b[47m',
43
+ brightblack: '\x1b[100m',brightred: '\x1b[101m',brightgreen: '\x1b[102m',
44
+ brightyellow: '\x1b[103m',brightblue: '\x1b[104m',brightmagenta: '\x1b[105m',
45
+ brightcyan: '\x1b[106m',brightwhite: '\x1b[107m',
46
+ grey: '\x1b[100m',gray: '\x1b[100m',orange: '\x1b[43m',
47
+ pink: '\x1b[105m',purple: '\x1b[45m', default: '\x1b[49m',
48
+ };
49
+
50
+ // SGR text style codes \textType{type}
51
+ const TEXT_TYPES = {
52
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
53
+ italic: '\x1b[3m', underline: '\x1b[4m', blink: '\x1b[5m',
54
+ rapidblink: '\x1b[6m', reverse: '\x1b[7m', hidden: '\x1b[8m',
55
+ strikethrough: '\x1b[9m',
56
+ // reset variants
57
+ resetbold: '\x1b[21m', resetdim: '\x1b[22m', resetitalic: '\x1b[23m',
58
+ resetunderline:'\x1b[24m', resetblink: '\x1b[25m', resetreverse: '\x1b[27m',
59
+ resethidden: '\x1b[28m', resetstrike: '\x1b[29m',
60
+ // extra
61
+ overline: '\x1b[53m', resetoverline: '\x1b[55m',
62
+ doubleunderline:'\x1b[21m',
63
+ // aliases
64
+ b: '\x1b[1m', i: '\x1b[3m', u: '\x1b[4m',
65
+ s: '\x1b[9m', r: '\x1b[0m', inv: '\x1b[7m',
66
+ };
67
+
68
+ // All built-in named escape sequences (no args)
69
+ // \name → value
70
+ const ESCAPE_SEQUENCES = {
71
+ // classic single-char
72
+ 'n': '\n', 't': '\t', 'r': '\r', '\\': '\\',
73
+ "'": "'", '"': '"', '0': '\0', 'a': '\x07',
74
+ 'b': '\x08','v': '\x0B','f': '\x0C','e': '\x1b',
75
+ // ANSI reset / clear
76
+ 'reset': '\x1b[0m',
77
+ 'clr': '\x1b[0m',
78
+ // text styles (no-arg shortcuts)
79
+ 'bold': '\x1b[1m',
80
+ 'dim': '\x1b[2m',
81
+ 'italic': '\x1b[3m',
82
+ 'underline': '\x1b[4m',
83
+ 'blink': '\x1b[5m',
84
+ 'rapidblink': '\x1b[6m',
85
+ 'reverse': '\x1b[7m',
86
+ 'hidden': '\x1b[8m',
87
+ 'strike': '\x1b[9m',
88
+ 'overline': '\x1b[53m',
89
+ 'resetbold': '\x1b[22m',
90
+ 'resetdim': '\x1b[22m',
91
+ 'resetitalic':'\x1b[23m',
92
+ 'resetul': '\x1b[24m',
93
+ 'resetblink': '\x1b[25m',
94
+ 'resetrev': '\x1b[27m',
95
+ // cursor movement (fixed)
96
+ 'home': '\x1b[H',
97
+ 'cls': '\x1b[2J', // clear screen
98
+ 'clearline': '\x1b[2K', // clear current line
99
+ 'clearright': '\x1b[0K', // clear line right of cursor
100
+ 'clearleft': '\x1b[1K', // clear line left of cursor
101
+ 'cleardown': '\x1b[0J', // clear screen below cursor
102
+ 'clearup': '\x1b[1J', // clear screen above cursor
103
+ 'savecursor': '\x1b[s', // save cursor position
104
+ 'restorecursor':'\x1b[u', // restore cursor position
105
+ 'hidecursor': '\x1b[?25l', // hide cursor
106
+ 'showcursor': '\x1b[?25h', // show cursor
107
+ 'cursorup': '\x1b[1A',
108
+ 'cursordown': '\x1b[1B',
109
+ 'cursorright':'\x1b[1C',
110
+ 'cursorleft': '\x1b[1D',
111
+ 'cursorbol': '\x1b[0G', // cursor to beginning of line
112
+ // terminal modes
113
+ 'altscreen': '\x1b[?1049h', // enter alternate screen buffer
114
+ 'mainscreen': '\x1b[?1049l', // exit alternate screen buffer
115
+ 'wrap': '\x1b[?7h', // enable line wrap
116
+ 'nowrap': '\x1b[?7l', // disable line wrap
117
+ // foreground color shortcuts
118
+ 'black': '\x1b[30m', 'red': '\x1b[31m', 'green': '\x1b[32m',
119
+ 'yellow': '\x1b[33m', 'blue': '\x1b[34m', 'magenta': '\x1b[35m',
120
+ 'cyan': '\x1b[36m', 'white': '\x1b[37m',
121
+ // background shortcuts
122
+ 'bgblack': '\x1b[40m', 'bgred': '\x1b[41m', 'bggreen': '\x1b[42m',
123
+ 'bgyellow': '\x1b[43m', 'bgblue': '\x1b[44m', 'bgmagenta':'\x1b[45m',
124
+ 'bgcyan': '\x1b[46m', 'bgwhite': '\x1b[47m',
125
+ // misc chars
126
+ 'bell': '\x07',
127
+ 'tab': '\t',
128
+ 'cr': '\r',
129
+ 'lf': '\n',
130
+ 'crlf': '\r\n',
131
+ 'null': '\0',
132
+ 'esc': '\x1b',
133
+ 'space': ' ',
134
+ 'nbsp': '\u00A0',
135
+ 'zwsp': '\u200B', // zero-width space
136
+ 'zwnj': '\u200C',
137
+ 'zwj': '\u200D',
138
+ // box-drawing
139
+ 'hline': '\u2500', // ─
140
+ 'vline': '\u2502', // │
141
+ 'tlcorner': '\u250C', // ┌
142
+ 'trcorner': '\u2510', // ┐
143
+ 'blcorner': '\u2514', // └
144
+ 'brcorner': '\u2518', // ┘
145
+ 'ttee': '\u252C', // ┬
146
+ 'btee': '\u2534', // ┴
147
+ 'ltee': '\u251C', // ├
148
+ 'rtee': '\u2524', // ┤
149
+ 'cross': '\u253C', // ┼
150
+ // double box
151
+ 'dhline': '\u2550', // ═
152
+ 'dvline': '\u2551', // ║
153
+ 'dtlcorner': '\u2554', // ╔
154
+ 'dtrcorner': '\u2557', // ╗
155
+ 'dblcorner': '\u255A', // ╚
156
+ 'dbrcorner': '\u255D', // ╝
157
+ // arrows
158
+ 'uarr': '\u2191', // ↑
159
+ 'darr': '\u2193', // ↓
160
+ 'larr': '\u2190', // ←
161
+ 'rarr': '\u2192', // →
162
+ 'udarr': '\u2195', // ↕
163
+ 'lrarr': '\u2194', // ↔
164
+ // misc unicode
165
+ 'check': '\u2713', // ✓
166
+ 'cross2': '\u2717', // ✗
167
+ 'bullet': '\u2022', // •
168
+ 'ellipsis': '\u2026', // …
169
+ 'degree': '\u00B0', // °
170
+ 'copyright': '\u00A9', // ©
171
+ 'registered': '\u00AE', // ®
172
+ 'trademark': '\u2122', // ™
173
+ 'section': '\u00A7', // §
174
+ 'dagger': '\u2020', // †
175
+ 'middot': '\u00B7', // ·
176
+ 'endash': '\u2013', // –
177
+ 'emdash': '\u2014', // —
178
+ };
179
+
180
+ // Argument-taking built-in escape handlers: \NAME{arg1, arg2, ...} → string
181
+ // Each is a function (args: string[]) → string
182
+ const ESCAPE_HANDLERS = {
183
+ // \color{name} — set foreground color by name
184
+ color: (args) => {
185
+ const raw = (args[0] || '').trim();
186
+ // reassemble if split on inner comma: rgb:255, 100, 0 → rgb:255,100,0
187
+ const name = (args.length > 1 && (raw.startsWith('rgb:') || raw.startsWith('256:')))
188
+ ? args.join(',').toLowerCase().replace(/\s/g,'')
189
+ : raw.toLowerCase();
190
+ if (FG[name]) return FG[name];
191
+ // 256-color: \color{256:N}
192
+ if (name.startsWith('256:')) return `\x1b[38;5;${name.slice(4)}m`;
193
+ // RGB: \color{rgb:R,G,B}
194
+ if (name.startsWith('rgb:')) { const [r,g,b] = name.slice(4).split(','); return `\x1b[38;2;${r};${g};${b}m`; }
195
+ // hex: \color{#RRGGBB}
196
+ if (name.startsWith('#') && name.length === 7) {
197
+ const r = parseInt(name.slice(1,3),16), g = parseInt(name.slice(3,5),16), b = parseInt(name.slice(5,7),16);
198
+ return `\x1b[38;2;${r};${g};${b}m`;
199
+ }
200
+ return FG.default;
201
+ },
202
+ // \bg{name} — set background color by name
203
+ bg: (args) => {
204
+ const raw = (args[0] || '').trim();
205
+ const name = (args.length > 1 && (raw.startsWith('rgb:') || raw.startsWith('256:')))
206
+ ? args.join(',').toLowerCase().replace(/\s/g,'')
207
+ : raw.toLowerCase();
208
+ if (BG[name]) return BG[name];
209
+ if (name.startsWith('256:')) return `\x1b[48;5;${name.slice(4)}m`;
210
+ if (name.startsWith('rgb:')) { const [r,g,b] = name.slice(4).split(','); return `\x1b[48;2;${r};${g};${b}m`; }
211
+ if (name.startsWith('#') && name.length === 7) {
212
+ const r = parseInt(name.slice(1,3),16), g = parseInt(name.slice(3,5),16), b = parseInt(name.slice(5,7),16);
213
+ return `\x1b[48;2;${r};${g};${b}m`;
214
+ }
215
+ return BG.default;
216
+ },
217
+ // \textType{type} — set text style / SGR attribute
218
+ textType: (args) => {
219
+ const t = (args[0] || '').toLowerCase().trim();
220
+ if (TEXT_TYPES[t]) return TEXT_TYPES[t];
221
+ // raw SGR code: \textType{33}
222
+ if (/^\d+$/.test(t)) return `\x1b[${t}m`;
223
+ return '';
224
+ },
225
+ // \sgr{n} — raw SGR escape: \x1b[Nm
226
+ sgr: (args) => args.map(a => `\x1b[${a.trim()}m`).join(''),
227
+ // \cursor{up|down|left|right, N} — move cursor N cells
228
+ cursor: (args) => {
229
+ const dir = (args[0] || '').toLowerCase().trim();
230
+ const n = parseInt(args[1]) || 1;
231
+ const codes = { up:'A', down:'B', right:'C', left:'D', nextline:'E', prevline:'F', col:'G', row:'d' };
232
+ return codes[dir] ? `\x1b[${n}${codes[dir]}` : '';
233
+ },
234
+ // \pos{row,col} — move cursor to absolute position
235
+ pos: (args) => {
236
+ const row = parseInt(args[0]) || 1, col = parseInt(args[1]) || 1;
237
+ return `\x1b[${row};${col}H`;
238
+ },
239
+ // \erase{mode} — erase display or line
240
+ erase: (args) => {
241
+ const mode = (args[0] || 'screen').toLowerCase().trim();
242
+ const m = { screen:'2J', 'screen-down':'0J', 'screen-up':'1J', line:'2K', 'line-right':'0K', 'line-left':'1K' };
243
+ return m[mode] ? `\x1b[${m[mode]}` : '';
244
+ },
245
+ // \scroll{up|down, N} — scroll terminal
246
+ scroll: (args) => {
247
+ const dir = (args[0] || 'up').toLowerCase().trim();
248
+ const n = parseInt(args[1]) || 1;
249
+ return dir === 'down' ? `\x1b[${n}T` : `\x1b[${n}S`;
250
+ },
251
+ // \unicode{codepoint} — literal unicode char: \unicode{U+1F600} or \unicode{128512}
252
+ unicode: (args) => {
253
+ const raw = (args[0] || '').trim().replace(/^U\+/i,'');
254
+ const cp = parseInt(raw, 16);
255
+ return isNaN(cp) ? '' : String.fromCodePoint(cp);
256
+ },
257
+ // \hex{HH HH ...} — raw hex bytes
258
+ hex: (args) => args.map(a => String.fromCharCode(parseInt(a.trim(),16))).join(''),
259
+ // \repeat{char, N} — repeat a character N times
260
+ repeat: (args) => {
261
+ const ch = args[0] || ' ', n = parseInt(args[1]) || 1;
262
+ return ch.repeat(n);
263
+ },
264
+ // \pad{text, width, char} — pad text to width
265
+ pad: (args) => {
266
+ const text = args[0] || '', width = parseInt(args[1]) || 0, ch = args[2] || ' ';
267
+ return text.padEnd(width, ch);
268
+ },
269
+ // \padl{text, width, char} — left-pad
270
+ padl: (args) => {
271
+ const text = args[0] || '', width = parseInt(args[1]) || 0, ch = args[2] || ' ';
272
+ return text.padStart(width, ch);
273
+ },
274
+ // \upper{text} — uppercase at lex time
275
+ upper: (args) => (args[0] || '').toUpperCase(),
276
+ // \lower{text} — lowercase at lex time
277
+ lower: (args) => (args[0] || '').toLowerCase(),
278
+ // \link{url, text} — OSC 8 hyperlink (supported in iTerm2, Kitty, WezTerm)
279
+ link: (args) => `\x1b]8;;${args[0] || ''}\x1b\\${args[1] || args[0] || ''}\x1b]8;;\x1b\\`,
280
+ // \title{text} — set terminal window title
281
+ title: (args) => `\x1b]0;${args[0] || ''}\x07`,
282
+ // \notify{title, body} — terminal desktop notification (iTerm2/Kitty)
283
+ notify: (args) => `\x1b]9;${args[0] || ''}${args[1] ? ' ' + args[1] : ''}\x07`,
284
+ // \img{path, width, height} — Kitty/iTerm2 inline image protocol stub
285
+ img: (args) => `\x1b]1337;File=inline=1;width=${args[1]||'auto'};height=${args[2]||'auto'}:${args[0]||''}\x07`,
286
+ // \256fg{N} — 256-color foreground by number
287
+ '256fg': (args) => `\x1b[38;5;${parseInt(args[0])||0}m`,
288
+ // \256bg{N} — 256-color background by number
289
+ '256bg': (args) => `\x1b[48;5;${parseInt(args[0])||0}m`,
290
+ // \rgbfg{R,G,B} — true-color foreground
291
+ rgbfg: (args) => `\x1b[38;2;${parseInt(args[0])||0};${parseInt(args[1])||0};${parseInt(args[2])||0}m`,
292
+ // \rgbbg{R,G,B} — true-color background
293
+ rgbbg: (args) => `\x1b[48;2;${parseInt(args[0])||0};${parseInt(args[1])||0};${parseInt(args[2])||0}m`,
294
+ };
295
+
296
+ // Global custom escape registry — populated by #register escape or register_escape statement
297
+ const CUSTOM_ESCAPES = new Map();
298
+
299
+ /**
300
+ * processEscape(name, args, source, line, col)
301
+ * Core escape resolver used by readString() and fstring().
302
+ * name - escape name (multi-char allowed)
303
+ * args - array of string arguments (empty if no {})
304
+ * Returns the resolved string value.
305
+ */
306
+ function processEscape(name, args, line, col) {
307
+ // 1. custom registry first (allows overriding builtins)
308
+ if (CUSTOM_ESCAPES.has(name)) {
309
+ const handler = CUSTOM_ESCAPES.get(name);
310
+ if (typeof handler === 'function') return handler(args);
311
+ if (typeof handler === 'string') return handler;
312
+ }
313
+ // 2. argument-taking built-ins
314
+ if (ESCAPE_HANDLERS[name]) return ESCAPE_HANDLERS[name](args);
315
+ // 3. no-arg built-ins
316
+ if (ESCAPE_SEQUENCES[name] !== undefined) return ESCAPE_SEQUENCES[name];
317
+ // 4. raw octal: \123
318
+ if (/^[0-7]+$/.test(name)) return String.fromCharCode(parseInt(name, 8));
319
+ // 5. raw hex: \xFF or \x1b
320
+ if (/^x[0-9a-fA-F]{1,4}$/.test(name)) return String.fromCharCode(parseInt(name.slice(1), 16));
321
+ // 6. unicode codepoint: \uXXXX \UXXXXXXXX
322
+ if (/^u[0-9a-fA-F]{4}$/.test(name)) return String.fromCodePoint(parseInt(name.slice(1), 16));
323
+ if (/^U[0-9a-fA-F]{8}$/.test(name)) return String.fromCodePoint(parseInt(name.slice(1), 16));
324
+ // 7. unknown — pass through literally (backslash + name)
325
+ return '\\' + name;
326
+ }
327
+
7
328
  const HOLLAND = {
8
329
  million: 1e6,
9
330
  billion: 1e9,
@@ -45,51 +366,74 @@ const LITERALS = {
45
366
  nfunc: () => Symbol('NOVA_NULL'),
46
367
  };
47
368
 
369
+ // ── Dynamic / Meta Variables ────────────────────────────────────────────────
370
+ // These identifiers are intercepted at lex time and emitted as DVAR tokens.
371
+ // The executor resolves them to their runtime values.
372
+ // `default` is a special sentinel: when passed as a function argument, the
373
+ // parameter's declared default value is used instead.
374
+ const DYNAMIC_VARS = new Set([
375
+ 'default', // use parameter's default value when passed as arg
376
+ '__line__', // current source line number
377
+ '__col__', // current source column number
378
+ '__file__', // absolute path of current file (or "<repl>")
379
+ '__dirname__', // directory of current file
380
+ '__func__', // name of the enclosing function (or "<top>")
381
+ '__date__', // ISO date string (YYYY-MM-DD) at runtime
382
+ '__time__', // ISO time string (HH:MM:SS) at runtime
383
+ '__timestamp__', // Unix timestamp in ms at runtime
384
+ '__datetime__', // Full ISO datetime string at runtime
385
+ '__version__', // Nova runtime version
386
+ '__pid__', // process.pid
387
+ '__platform__', // process.platform
388
+ '__arch__', // os.arch()
389
+ '__argv__', // process.argv
390
+ '__env__', // full process.env object
391
+ '__scope__', // reference to current scope
392
+ '__caller__', // name of calling function (one level up)
393
+ '__module__', // current module name (file stem or "<repl>")
394
+ '__namespace__', // active namespace name or null
395
+ '__random__', // random float 0..1 (regenerated each read)
396
+ '__uuid__', // a fresh UUID each read
397
+ '__iter__', // current loop iteration index (-1 outside loops)
398
+ '__stack__', // simplified call stack as array
399
+ ]);
400
+
401
+ // Keywords that are ONLY valid inside an @classic { } block.
402
+ // Outside @classic these identifiers are treated as plain IDENTIFIER tokens.
403
+ const CLASSIC_KEYWORDS = new Set([
404
+ 'foreach','temp','keep','echo','gear','engage','sandbox','infer','addto',
405
+ 'macro','snippet','defunc','lambda','compose','partial',
406
+ 'backup','retrieve','describe','classify','rate',
407
+ 'using','unuse',
408
+ 'lend','sstream','declare','session','enter','resu',
409
+ 'warn','info','skip','end','clear','time','keyfunc',
410
+ 'print','log','println','logln','expt','option','env','hello','eval',
411
+ 'register_escape',
412
+ ]);
413
+
48
414
  const KEYWORDS = new Set([
49
415
  // core
50
416
  'var','let','const','class','if','else','for','repeat','unless','until',
51
417
  'throw','try','catch','finally','func','function','return','give', 'yield', 'goback',
52
- 'async','await','do','while', 'Strict',
418
+ 'async','await','do','while', 'block',
53
419
  // control
54
420
  'switch','case','default','break','continue',
55
421
  'of','each','match','when','where','as',
56
422
  // meta
57
- 'assert','emit','on','new','extends',
423
+ 'assert','emit','on','new','extends', 'link',
58
424
  // types
59
425
  'type','struct','interface','enum','trait','impl',
60
426
  // accessors
61
427
  'get','set',
62
- // nova classic: iteration
63
- 'foreach',
64
- // nova classic: scoping
65
- 'temp','keep',
66
- // nova classic: output
67
- 'echo',
68
- // nova classic: gears
69
- 'gear','engage',
70
- // nova classic: isolation
71
- 'sandbox','infer',
72
- // nova classic: collections
73
- 'addto',
74
- // nova classic: code reuse
75
- 'macro','block','snippet','defunc','lambda','compose','partial',
76
- // nova classic: state
77
- 'backup','retrieve','rate','classify','describe',
78
- // nova classic: feature flags
79
- 'using','unuse',
80
- // nova classic: flow variants
81
- 'guard','when','with','loop','wait','run',
82
- 'lend','sstream','declare','session','enter',
83
- 'namespace','resu','export',
84
- 'skip','end','clear','time',
85
- // nova classic: output
86
- 'print','log','println','logln',
87
- // nova classic: misc
88
- 'keyfunc','resu',
89
- 'expt','option',
90
- 'env',
428
+ // smart variable modifiers
429
+ 'setter','getter','frozen','lazy','tracked','nonull','once',
430
+ // numeric constraint types
431
+ 'fnum','fint',
432
+ // classic compatibility block
433
+ 'classic',
91
434
  // misc
92
- 'hello', 'eval', 'import', 'import_builtin', 'from', 'export', 'default', 'namespace',
435
+ 'import', 'import_builtin', 'from', 'export', 'default', 'namespace',
436
+ 'guard','when','with','loop','wait','run', 'nat',
93
437
  // http / server
94
438
  'server', 'fetch',
95
439
  'post', 'put', 'delete', 'patch', 'head', 'options',
@@ -106,10 +450,25 @@ const KEYWORDS = new Set([
106
450
 
107
451
  const { CustomError, formatError } = require('./error');
108
452
 
109
- // All operators keyed by their symbol string.
453
+ // Helper: split a comma-separated arg string respecting nested {}, (), []
454
+ function splitArgs(str) {
455
+ const args = [];
456
+ let cur = '', depth = 0;
457
+ for (const ch of str) {
458
+ if (ch === '{' || ch === '(' || ch === '[') { depth++; cur += ch; }
459
+ else if (ch === '}' || ch === ')' || ch === ']') { depth--; cur += ch; }
460
+ else if (ch === ',' && depth === 0) { args.push(cur.trim()); cur = ''; }
461
+ else cur += ch;
462
+ }
463
+ if (cur.trim()) args.push(cur.trim());
464
+ return args;
465
+ }
466
+
467
+
110
468
  // Higher precedence = binds tighter.
111
469
  const OPERATORS = new Map([
112
470
  // assignment (0)
471
+ ['$', { precedence: 11, isUnary: true }],
113
472
  ['=', { precedence: 0 }],
114
473
  ['=>', { precedence: 0 }],
115
474
  ['|>', { precedence: 0 }],
@@ -210,11 +569,11 @@ const OPERATORS = new Map([
210
569
  ['index', { precedence: 14 }],
211
570
  ]);
212
571
 
213
- const PUNCTUATION = new Set(['(',')','{','}','[',']',',','.',':', ';','?','@','#']);
572
+ const PUNCTUATION = new Set(['(',')','{','}','[',']',',','.',':', ';','?','@','#', '\\']);
214
573
  // Note: '...' is already in OPERATORS; '::' as two-char operator below
215
574
 
216
575
  class Lexer {
217
- constructor(source) {
576
+ constructor(source, classicMode = false) {
218
577
  this.source = source;
219
578
  this.position = 0;
220
579
  this.line = 1;
@@ -222,6 +581,7 @@ class Lexer {
222
581
  this.tokens = [];
223
582
  this.definitions = new Map();
224
583
  this.isSkipping = false;
584
+ this.classicMode = classicMode; // true when tokenizing inside @classic { }
225
585
  }
226
586
 
227
587
  // ── preprocessor ──
@@ -267,6 +627,26 @@ class Lexer {
267
627
  const name = this.readIdentifier();
268
628
  OPERATORS.set(name, JSON.parse(this.readRestOfLine()));
269
629
  OPERATORS.get(name).custom = true;
630
+ } else if (dirArg === 'escape') {
631
+ // #register escape NAME replacement_string_or_function
632
+ const escapeName = this.readIdentifier();
633
+ const rest = this.readRestOfLine().trim();
634
+ // If rest is a quoted string, use as literal replacement
635
+ if ((rest.startsWith('"') && rest.endsWith('"')) ||
636
+ (rest.startsWith("'") && rest.endsWith("'"))) {
637
+ CUSTOM_ESCAPES.set(escapeName, rest.slice(1, -1)
638
+ .replace(/\\n/g, '\n').replace(/\\t/g, '\t')
639
+ .replace(/\\r/g, '\r').replace(/\\x1b/g, '\x1b'));
640
+ } else if (rest.startsWith('\\x') || rest.startsWith('\\u') || rest.startsWith('\\e')) {
641
+ // raw escape literal in the directive value
642
+ CUSTOM_ESCAPES.set(escapeName, rest.replace(/\\x([0-9a-fA-F]{2})/g, (_,h) => String.fromCharCode(parseInt(h,16)))
643
+ .replace(/\\u([0-9a-fA-F]{4})/g, (_,h) => String.fromCodePoint(parseInt(h,16)))
644
+ .replace(/\\e/g, '\x1b'));
645
+ } else {
646
+ // treat as a literal string value
647
+ CUSTOM_ESCAPES.set(escapeName, rest);
648
+ }
649
+ shouldSkip = false; // readRestOfLine already consumed the line
270
650
  }
271
651
  break;
272
652
  }
@@ -326,6 +706,10 @@ class Lexer {
326
706
  if (ident === 'f' && (this.peek() === '"' || this.peek() === "'")) {
327
707
  this.fstring(this.advance()); continue;
328
708
  }
709
+ // raw string prefix
710
+ if (ident === 'r' && (this.peek() === '"' || this.peek() === "'")) {
711
+ this.rstring(this.advance()); continue;
712
+ }
329
713
  // URL literal: http(s)://, ftp://, ws(s)://, content://, localhost(:port)?
330
714
  const col2 = this.column - ident.length - 1;
331
715
  const isScheme = ['http','https','ftp','ws','wss','content'].includes(ident);
@@ -368,8 +752,16 @@ class Lexer {
368
752
  const col = this.column - 1;
369
753
  if (Object.prototype.hasOwnProperty.call(LITERALS, value)) {
370
754
  this.addToken('LITERAL', LITERALS[value], col);
755
+ } else if (DYNAMIC_VARS.has(value)) {
756
+ // Dynamic/meta variable — bake source location into the token so the
757
+ // executor can report exact __line__/__col__ without re-scanning.
758
+ this.tokens.push({ type: 'DVAR', value, line: this.line, column: col, srcLine: this.line, srcCol: col });
371
759
  } else if (OPERATORS.has(value)) {
372
760
  this.addOperatorToken(value, col);
761
+ } else if (CLASSIC_KEYWORDS.has(value)) {
762
+ // Classic keywords only become KEYWORD tokens inside @classic { } blocks.
763
+ // Outside @classic they are plain identifiers so they don't pollute the core namespace.
764
+ this.addToken(this.classicMode ? 'KEYWORD' : 'IDENTIFIER', value, col);
373
765
  } else {
374
766
  this.addToken(KEYWORDS.has(value) ? 'KEYWORD' : 'IDENTIFIER', value, col);
375
767
  }
@@ -462,26 +854,75 @@ class Lexer {
462
854
  throw this.error('Unterminated block comment');
463
855
  }
464
856
 
857
+ // ── escape sequence reader ────────────────────────────────────────────
858
+ // Called after consuming the leading backslash inside any string context.
859
+ // Reads the escape name (one or more chars) and optional {arg1, arg2, ...} block.
860
+ // Returns the resolved string value.
861
+ readEscape() {
862
+ // Read escape name: one or more alphanumeric/underscore/dash chars OR a single symbol char
863
+ let name = '';
864
+ const first = this.peek();
865
+ if (!first || first === '\0') return '\\';
866
+
867
+ // Single-symbol escapes: \\ \' \" \{ \} \`
868
+ if (/[\\'"{}` ]/.test(first)) {
869
+ this.advance();
870
+ // Let processEscape handle single-char symbol escapes
871
+ return processEscape(first, [], this.line, this.column);
872
+ }
873
+
874
+ // Read multi-char name
875
+ while (!this.isAtEnd() && /[A-Za-z0-9_\-]/.test(this.peek())) name += this.advance();
876
+
877
+ // No name at all — treat as literal backslash
878
+ if (!name) {
879
+ const ch = this.advance();
880
+ return processEscape(ch, [], this.line, this.column);
881
+ }
882
+
883
+ // Read optional argument block: {arg1, arg2, ...}
884
+ let args = [];
885
+ if (this.peek() === '{') {
886
+ this.advance(); // consume {
887
+ let argStr = '', depth = 1;
888
+ while (!this.isAtEnd() && depth > 0) {
889
+ const ch = this.peek();
890
+ if (ch === '{') { depth++; argStr += this.advance(); }
891
+ else if (ch === '}') { depth--; if (depth > 0) argStr += this.advance(); else this.advance(); }
892
+ else if (ch === '\n') { this.line++; this.column = 1; this.position++; argStr += '\n'; }
893
+ else argStr += this.advance();
894
+ }
895
+ // Split on top-level commas (not nested in brackets/parens)
896
+ args = splitArgs(argStr);
897
+ }
898
+
899
+ return processEscape(name, args, this.line, this.column);
900
+ }
901
+
465
902
  readString(quote) {
466
903
  const col = this.column;
467
904
  let value = '';
468
905
  while (!this.isAtEnd() && this.peek() !== quote) {
469
- if (this.peek() === '\n') this.line++;
470
- if (this.peek() === '\\') {
471
- this.advance();
472
- const esc = this.advance();
473
- switch (esc) {
474
- case 'n': value += '\n'; break; case 't': value += '\t'; break;
475
- case 'r': value += '\r'; break; case '\\': value += '\\'; break;
476
- case "'": value += "'"; break; case '"': value += '"'; break;
477
- default: value += esc; break;
478
- }
479
- continue;
480
- }
481
- value += this.advance();
906
+ const ch = this.advance();
907
+ if (ch === '\n') { this.line++; this.column = 1; value += ch; continue; }
908
+ if (ch === '\\') { value += this.readEscape(); continue; }
909
+ value += ch;
482
910
  }
483
911
  if (this.isAtEnd()) throw this.error('Unterminated string literal');
484
- this.advance();
912
+ this.advance(); // consume closing quote
913
+ this.addToken('STRING', value, col);
914
+ }
915
+
916
+ rstring(quote) {
917
+ const col = this.column;
918
+ let value = '';
919
+ while (!this.isAtEnd() && this.peek() !== quote) {
920
+ const ch = this.advance();
921
+ if (ch === '\n') { this.line++; this.column = 1; value += ch; continue; }
922
+ value += ch;
923
+ }
924
+ if (this.isAtEnd()) throw this.error('Unterminated raw string literal');
925
+ this.advance(); // consume closing quote
485
926
  this.addToken('STRING', value, col);
486
927
  }
487
928
 
@@ -492,7 +933,13 @@ class Lexer {
492
933
  const ch = this.advance();
493
934
  if (ch === q && this.peek() === next) {
494
935
  this.advance();
495
- this.addToken('IDENTIFIER', value.trim(), col);
936
+ const fullName = '__' + value.trim() + '__';
937
+ if (DYNAMIC_VARS.has(fullName)) {
938
+ // Emit as DVAR token with baked-in source location
939
+ this.tokens.push({ type: 'DVAR', value: fullName, line: this.line, column: col, srcLine: this.line, srcCol: col });
940
+ } else {
941
+ this.addToken('IDENTIFIER', fullName, col);
942
+ }
496
943
  return;
497
944
  }
498
945
  value += ch;
@@ -518,7 +965,7 @@ class Lexer {
518
965
  if (c === '{') depth++; else if (c === '}') depth--;
519
966
  if (depth > 0) expr += c;
520
967
  }
521
- const innerLexer = new Lexer(expr);
968
+ const innerLexer = new Lexer(expr, this.classicMode);
522
969
  innerLexer.definitions = new Map(this.definitions); // inherit macros
523
970
  const innerTokens = innerLexer.tokenize();
524
971
  innerTokens.pop();
@@ -526,8 +973,9 @@ class Lexer {
526
973
  this.addToken('INTERPOLATION_END', '}', this.column - 1);
527
974
  continue;
528
975
  }
976
+ if (ch === '\\') { value += this.readEscape(); continue; }
529
977
  if (ch === quote) { flush(); this.addToken('FSTRING_END', 'f"', this.column - 1); return; }
530
- if (ch === '\n') this.line++;
978
+ if (ch === '\n') { this.line++; this.column = 1; value += ch; continue; }
531
979
  value += ch;
532
980
  }
533
981
  throw this.error('Unterminated f-string');
@@ -575,4 +1023,4 @@ class Lexer {
575
1023
  }
576
1024
  }
577
1025
 
578
- module.exports = { Lexer, KEYWORDS, OPERATORS, PUNCTUATION };
1026
+ module.exports = { Lexer, KEYWORDS, CLASSIC_KEYWORDS, OPERATORS, PUNCTUATION, CUSTOM_ESCAPES, processEscape, ESCAPE_SEQUENCES, ESCAPE_HANDLERS, DYNAMIC_VARS };