naidejs 1.0.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 +21 -0
- package/README.md +171 -0
- package/SPEC-X.nx +144 -0
- package/SPEC.naide +182 -0
- package/bin/naide.js +124 -0
- package/examples/async.naide +50 -0
- package/examples/async.nx +40 -0
- package/examples/crud.naide +79 -0
- package/examples/crud.nx +64 -0
- package/examples/hello.naide +33 -0
- package/examples/hello.nx +29 -0
- package/examples/model.naide +44 -0
- package/examples/server.naide +28 -0
- package/examples/server.nx +27 -0
- package/package.json +46 -0
- package/src/generator.js +573 -0
- package/src/index.js +23 -0
- package/src/lexer.js +389 -0
- package/src/parser.js +1079 -0
- package/src/preprocess.js +286 -0
- package/src/tokens.js +162 -0
package/src/lexer.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { T, KEYWORDS } from './tokens.js';
|
|
2
|
+
|
|
3
|
+
class Token {
|
|
4
|
+
constructor(type, value, line, col) {
|
|
5
|
+
this.type = type;
|
|
6
|
+
this.value = value;
|
|
7
|
+
this.line = line;
|
|
8
|
+
this.col = col;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Lexer {
|
|
13
|
+
constructor(source) {
|
|
14
|
+
this.source = source;
|
|
15
|
+
this.pos = 0;
|
|
16
|
+
this.line = 1;
|
|
17
|
+
this.col = 1;
|
|
18
|
+
this.tokens = [];
|
|
19
|
+
this.indentStack = [0];
|
|
20
|
+
this.atLineStart = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
peek() {
|
|
24
|
+
return this.pos < this.source.length ? this.source[this.pos] : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
advance() {
|
|
28
|
+
const ch = this.source[this.pos++];
|
|
29
|
+
if (ch === '\n') {
|
|
30
|
+
this.line++;
|
|
31
|
+
this.col = 1;
|
|
32
|
+
} else {
|
|
33
|
+
this.col++;
|
|
34
|
+
}
|
|
35
|
+
return ch;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lookAhead(n = 1) {
|
|
39
|
+
return this.pos + n < this.source.length ? this.source[this.pos + n] : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
makeToken(type, value) {
|
|
43
|
+
return new Token(type, value, this.line, this.col);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
tokenize() {
|
|
47
|
+
while (this.pos < this.source.length) {
|
|
48
|
+
if (this.atLineStart) {
|
|
49
|
+
this.handleIndentation();
|
|
50
|
+
this.atLineStart = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ch = this.peek();
|
|
54
|
+
if (ch === null) break;
|
|
55
|
+
|
|
56
|
+
if (ch === '\n') {
|
|
57
|
+
this.advance();
|
|
58
|
+
// Skip consecutive newlines
|
|
59
|
+
if (this.tokens.length > 0 && this.tokens[this.tokens.length - 1].type !== T.NEWLINE) {
|
|
60
|
+
this.tokens.push(this.makeToken(T.NEWLINE, '\n'));
|
|
61
|
+
}
|
|
62
|
+
this.atLineStart = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (ch === '\r') {
|
|
67
|
+
this.advance();
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (ch === ' ' || ch === '\t') {
|
|
72
|
+
this.advance();
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (ch === '#') {
|
|
77
|
+
this.skipComment();
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (ch === '"' || ch === "'") {
|
|
82
|
+
this.readString(ch);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (ch === '`') {
|
|
87
|
+
this.readTemplateString();
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.isDigit(ch)) {
|
|
92
|
+
this.readNumber();
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.isIdentStart(ch)) {
|
|
97
|
+
this.readIdentifier();
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.readOperator();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Emit remaining DEDENTs
|
|
105
|
+
while (this.indentStack.length > 1) {
|
|
106
|
+
this.indentStack.pop();
|
|
107
|
+
this.tokens.push(this.makeToken(T.DEDENT, ''));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.tokens.push(this.makeToken(T.EOF, ''));
|
|
111
|
+
return this.tokens;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
handleIndentation() {
|
|
115
|
+
let indent = 0;
|
|
116
|
+
while (this.pos < this.source.length) {
|
|
117
|
+
const ch = this.source[this.pos];
|
|
118
|
+
if (ch === ' ') {
|
|
119
|
+
indent++;
|
|
120
|
+
this.pos++;
|
|
121
|
+
this.col++;
|
|
122
|
+
} else if (ch === '\t') {
|
|
123
|
+
indent += 2;
|
|
124
|
+
this.pos++;
|
|
125
|
+
this.col++;
|
|
126
|
+
} else {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Skip blank lines and comment-only lines
|
|
132
|
+
if (this.pos >= this.source.length || this.source[this.pos] === '\n' || this.source[this.pos] === '#') {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const currentIndent = this.indentStack[this.indentStack.length - 1];
|
|
137
|
+
|
|
138
|
+
if (indent > currentIndent) {
|
|
139
|
+
this.indentStack.push(indent);
|
|
140
|
+
this.tokens.push(this.makeToken(T.INDENT, indent));
|
|
141
|
+
} else if (indent < currentIndent) {
|
|
142
|
+
while (this.indentStack.length > 1 && this.indentStack[this.indentStack.length - 1] > indent) {
|
|
143
|
+
this.indentStack.pop();
|
|
144
|
+
this.tokens.push(this.makeToken(T.DEDENT, ''));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
skipComment() {
|
|
150
|
+
while (this.pos < this.source.length && this.source[this.pos] !== '\n') {
|
|
151
|
+
this.advance();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
readString(quote) {
|
|
156
|
+
const startLine = this.line;
|
|
157
|
+
const startCol = this.col;
|
|
158
|
+
this.advance(); // skip opening quote
|
|
159
|
+
|
|
160
|
+
let parts = [];
|
|
161
|
+
let current = '';
|
|
162
|
+
|
|
163
|
+
while (this.pos < this.source.length && this.peek() !== quote) {
|
|
164
|
+
if (this.peek() === '\\') {
|
|
165
|
+
this.advance();
|
|
166
|
+
const esc = this.advance();
|
|
167
|
+
switch (esc) {
|
|
168
|
+
case 'n': current += '\n'; break;
|
|
169
|
+
case 't': current += '\t'; break;
|
|
170
|
+
case 'r': current += '\r'; break;
|
|
171
|
+
case '\\': current += '\\'; break;
|
|
172
|
+
case '"': current += '"'; break;
|
|
173
|
+
case "'": current += "'"; break;
|
|
174
|
+
case '{': current += '{'; break;
|
|
175
|
+
default: current += '\\' + esc;
|
|
176
|
+
}
|
|
177
|
+
} else if (this.peek() === '{' && quote === '"') {
|
|
178
|
+
// String interpolation - only in double quotes
|
|
179
|
+
if (current) {
|
|
180
|
+
parts.push({ type: 'text', value: current });
|
|
181
|
+
current = '';
|
|
182
|
+
}
|
|
183
|
+
this.advance(); // skip {
|
|
184
|
+
let expr = '';
|
|
185
|
+
let depth = 1;
|
|
186
|
+
while (this.pos < this.source.length && depth > 0) {
|
|
187
|
+
if (this.peek() === '{') depth++;
|
|
188
|
+
if (this.peek() === '}') depth--;
|
|
189
|
+
if (depth > 0) expr += this.advance();
|
|
190
|
+
else this.advance(); // skip closing }
|
|
191
|
+
}
|
|
192
|
+
parts.push({ type: 'expr', value: expr.trim() });
|
|
193
|
+
} else {
|
|
194
|
+
current += this.advance();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.peek() === quote) this.advance(); // skip closing quote
|
|
199
|
+
|
|
200
|
+
if (current) parts.push({ type: 'text', value: current });
|
|
201
|
+
|
|
202
|
+
if (parts.length === 0) {
|
|
203
|
+
this.tokens.push(new Token(T.STRING, { parts: [{ type: 'text', value: '' }], raw: '' }, startLine, startCol));
|
|
204
|
+
} else if (parts.length === 1 && parts[0].type === 'text') {
|
|
205
|
+
this.tokens.push(new Token(T.STRING, { parts, raw: parts[0].value }, startLine, startCol));
|
|
206
|
+
} else {
|
|
207
|
+
this.tokens.push(new Token(T.STRING, { parts, raw: null }, startLine, startCol));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
readTemplateString() {
|
|
212
|
+
const startLine = this.line;
|
|
213
|
+
const startCol = this.col;
|
|
214
|
+
this.advance(); // skip `
|
|
215
|
+
let value = '';
|
|
216
|
+
while (this.pos < this.source.length && this.peek() !== '`') {
|
|
217
|
+
if (this.peek() === '\\') {
|
|
218
|
+
this.advance();
|
|
219
|
+
value += this.advance();
|
|
220
|
+
} else {
|
|
221
|
+
value += this.advance();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (this.peek() === '`') this.advance();
|
|
225
|
+
this.tokens.push(new Token(T.STRING, { parts: [{ type: 'text', value }], raw: value }, startLine, startCol));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
readNumber() {
|
|
229
|
+
const startLine = this.line;
|
|
230
|
+
const startCol = this.col;
|
|
231
|
+
let num = '';
|
|
232
|
+
let isFloat = false;
|
|
233
|
+
|
|
234
|
+
while (this.pos < this.source.length && (this.isDigit(this.peek()) || this.peek() === '.' || this.peek() === '_')) {
|
|
235
|
+
if (this.peek() === '.') {
|
|
236
|
+
if (isFloat) break;
|
|
237
|
+
if (this.lookAhead() === '.') break; // range operator ..
|
|
238
|
+
isFloat = true;
|
|
239
|
+
}
|
|
240
|
+
if (this.peek() !== '_') {
|
|
241
|
+
num += this.peek();
|
|
242
|
+
}
|
|
243
|
+
this.advance();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.tokens.push(new Token(T.NUMBER, num, startLine, startCol));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
readIdentifier() {
|
|
250
|
+
const startLine = this.line;
|
|
251
|
+
const startCol = this.col;
|
|
252
|
+
let ident = '';
|
|
253
|
+
|
|
254
|
+
while (this.pos < this.source.length && this.isIdentChar(this.peek())) {
|
|
255
|
+
ident += this.advance();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check for compound keywords
|
|
259
|
+
if (ident === 'fn' && this.peek() === '.') {
|
|
260
|
+
const savedPos = this.pos;
|
|
261
|
+
const savedCol = this.col;
|
|
262
|
+
this.advance(); // skip .
|
|
263
|
+
let sub = '';
|
|
264
|
+
while (this.pos < this.source.length && this.isIdentChar(this.peek())) {
|
|
265
|
+
sub += this.advance();
|
|
266
|
+
}
|
|
267
|
+
if (sub === 'async') {
|
|
268
|
+
this.tokens.push(new Token(T.FN_ASYNC, 'fn.async', startLine, startCol));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// Restore if not a compound keyword
|
|
272
|
+
this.pos = savedPos;
|
|
273
|
+
this.col = savedCol;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (ident === 'await' && this.peek() === '.') {
|
|
277
|
+
const savedPos = this.pos;
|
|
278
|
+
const savedCol = this.col;
|
|
279
|
+
this.advance();
|
|
280
|
+
let sub = '';
|
|
281
|
+
while (this.pos < this.source.length && this.isIdentChar(this.peek())) {
|
|
282
|
+
sub += this.advance();
|
|
283
|
+
}
|
|
284
|
+
if (sub === 'all') {
|
|
285
|
+
this.tokens.push(new Token(T.AWAIT_ALL, 'await.all', startLine, startCol));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.pos = savedPos;
|
|
289
|
+
this.col = savedCol;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (ident === 'ret' && this.peek() === '.') {
|
|
293
|
+
// ret.status, ret.json etc - keep as IDENT with dot access
|
|
294
|
+
this.tokens.push(new Token(T.RET, ident, startLine, startCol));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const keyword = KEYWORDS[ident];
|
|
299
|
+
if (keyword) {
|
|
300
|
+
this.tokens.push(new Token(keyword, ident, startLine, startCol));
|
|
301
|
+
} else {
|
|
302
|
+
this.tokens.push(new Token(T.IDENT, ident, startLine, startCol));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
readOperator() {
|
|
307
|
+
const startLine = this.line;
|
|
308
|
+
const startCol = this.col;
|
|
309
|
+
const ch = this.advance();
|
|
310
|
+
|
|
311
|
+
switch (ch) {
|
|
312
|
+
case '=':
|
|
313
|
+
if (this.peek() === '=') { this.advance(); this.tokens.push(new Token(T.EQ, '==', startLine, startCol)); }
|
|
314
|
+
else if (this.peek() === '>') { this.advance(); this.tokens.push(new Token(T.FAT_ARROW, '=>', startLine, startCol)); }
|
|
315
|
+
else this.tokens.push(new Token(T.ASSIGN, '=', startLine, startCol));
|
|
316
|
+
break;
|
|
317
|
+
case '+':
|
|
318
|
+
if (this.peek() === '=') { this.advance(); this.tokens.push(new Token(T.PLUS_ASSIGN, '+=', startLine, startCol)); }
|
|
319
|
+
else this.tokens.push(new Token(T.PLUS, '+', startLine, startCol));
|
|
320
|
+
break;
|
|
321
|
+
case '-':
|
|
322
|
+
if (this.peek() === '=') { this.advance(); this.tokens.push(new Token(T.MINUS_ASSIGN, '-=', startLine, startCol)); }
|
|
323
|
+
else if (this.peek() === '>') { this.advance(); this.tokens.push(new Token(T.ARROW, '->', startLine, startCol)); }
|
|
324
|
+
else this.tokens.push(new Token(T.MINUS, '-', startLine, startCol));
|
|
325
|
+
break;
|
|
326
|
+
case '*':
|
|
327
|
+
this.tokens.push(new Token(T.STAR, '*', startLine, startCol));
|
|
328
|
+
break;
|
|
329
|
+
case '/':
|
|
330
|
+
this.tokens.push(new Token(T.SLASH, '/', startLine, startCol));
|
|
331
|
+
break;
|
|
332
|
+
case '%':
|
|
333
|
+
this.tokens.push(new Token(T.PERCENT, '%', startLine, startCol));
|
|
334
|
+
break;
|
|
335
|
+
case '!':
|
|
336
|
+
if (this.peek() === '=') { this.advance(); this.tokens.push(new Token(T.NEQ, '!=', startLine, startCol)); }
|
|
337
|
+
else this.tokens.push(new Token(T.NOT, '!', startLine, startCol));
|
|
338
|
+
break;
|
|
339
|
+
case '>':
|
|
340
|
+
if (this.peek() === '=') { this.advance(); this.tokens.push(new Token(T.GTE, '>=', startLine, startCol)); }
|
|
341
|
+
else this.tokens.push(new Token(T.GT, '>', startLine, startCol));
|
|
342
|
+
break;
|
|
343
|
+
case '<':
|
|
344
|
+
if (this.peek() === '=') { this.advance(); this.tokens.push(new Token(T.LTE, '<=', startLine, startCol)); }
|
|
345
|
+
else this.tokens.push(new Token(T.LT, '<', startLine, startCol));
|
|
346
|
+
break;
|
|
347
|
+
case '|':
|
|
348
|
+
if (this.peek() === '>') { this.advance(); this.tokens.push(new Token(T.PIPE, '|>', startLine, startCol)); }
|
|
349
|
+
else this.tokens.push(new Token(T.IDENT, '|', startLine, startCol));
|
|
350
|
+
break;
|
|
351
|
+
case '?':
|
|
352
|
+
if (this.peek() === '?') { this.advance(); this.tokens.push(new Token(T.NULLISH, '??', startLine, startCol)); }
|
|
353
|
+
else if (this.peek() === '.') { this.advance(); this.tokens.push(new Token(T.OPTIONAL, '?.', startLine, startCol)); }
|
|
354
|
+
else this.tokens.push(new Token(T.IDENT, '?', startLine, startCol));
|
|
355
|
+
break;
|
|
356
|
+
case '.':
|
|
357
|
+
if (this.peek() === '.') {
|
|
358
|
+
this.advance();
|
|
359
|
+
if (this.peek() === '.') { this.advance(); this.tokens.push(new Token(T.SPREAD, '...', startLine, startCol)); }
|
|
360
|
+
else this.tokens.push(new Token(T.RANGE, '..', startLine, startCol));
|
|
361
|
+
} else {
|
|
362
|
+
this.tokens.push(new Token(T.DOT, '.', startLine, startCol));
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
case '(': this.tokens.push(new Token(T.LPAREN, '(', startLine, startCol)); break;
|
|
366
|
+
case ')': this.tokens.push(new Token(T.RPAREN, ')', startLine, startCol)); break;
|
|
367
|
+
case '[': this.tokens.push(new Token(T.LBRACKET, '[', startLine, startCol)); break;
|
|
368
|
+
case ']': this.tokens.push(new Token(T.RBRACKET, ']', startLine, startCol)); break;
|
|
369
|
+
case '{': this.tokens.push(new Token(T.LBRACE, '{', startLine, startCol)); break;
|
|
370
|
+
case '}': this.tokens.push(new Token(T.RBRACE, '}', startLine, startCol)); break;
|
|
371
|
+
case ':': this.tokens.push(new Token(T.COLON, ':', startLine, startCol)); break;
|
|
372
|
+
case ',': this.tokens.push(new Token(T.COMMA, ',', startLine, startCol)); break;
|
|
373
|
+
default:
|
|
374
|
+
throw new Error(`Unexpected character '${ch}' at line ${startLine}:${startCol}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
isDigit(ch) {
|
|
379
|
+
return ch >= '0' && ch <= '9';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
isIdentStart(ch) {
|
|
383
|
+
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_' || ch === '$';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
isIdentChar(ch) {
|
|
387
|
+
return this.isIdentStart(ch) || this.isDigit(ch);
|
|
388
|
+
}
|
|
389
|
+
}
|