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.
- package/LICENSE +1 -1
- package/README.md +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libtea/tf.js +2691 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /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', '
|
|
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
|
-
//
|
|
63
|
-
'
|
|
64
|
-
//
|
|
65
|
-
'
|
|
66
|
-
//
|
|
67
|
-
'
|
|
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
|
-
'
|
|
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
|
-
//
|
|
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
|
-
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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 };
|