naidejs 1.2.0 → 1.3.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/package.json +1 -1
- package/src/index.js +26 -8
- package/src/lexer.js +17 -4
- package/src/parser.js +39 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naidejs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "NAIDE - Node AI Development Environment. AI-specialized language with built-in server, auth, DB, WebSocket, and more — transpiles to Node.js. Standard mode (~40% fewer tokens) and X mode (~80% fewer tokens).",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
package/src/index.js
CHANGED
|
@@ -4,16 +4,34 @@ import { Generator } from './generator.js';
|
|
|
4
4
|
import { preprocess } from './preprocess.js';
|
|
5
5
|
|
|
6
6
|
export function compile(source, { mode = 'naide', runtimePath } = {}) {
|
|
7
|
+
let processedSource = source;
|
|
7
8
|
if (mode === 'x') {
|
|
8
|
-
|
|
9
|
+
processedSource = preprocess(source);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const lexer = new Lexer(processedSource);
|
|
13
|
+
const tokens = lexer.tokenize();
|
|
14
|
+
const parser = new Parser(tokens);
|
|
15
|
+
const ast = parser.parse();
|
|
16
|
+
const generator = new Generator({ runtimePath });
|
|
17
|
+
const js = generator.generate(ast);
|
|
18
|
+
return { js, ast, tokens, naide: mode === 'x' ? processedSource : null };
|
|
19
|
+
} catch (e) {
|
|
20
|
+
const lineMatch = e.message.match(/line (\d+)/);
|
|
21
|
+
if (lineMatch) {
|
|
22
|
+
const lineNum = parseInt(lineMatch[1]);
|
|
23
|
+
const lines = processedSource.split('\n');
|
|
24
|
+
const start = Math.max(0, lineNum - 3);
|
|
25
|
+
const end = Math.min(lines.length, lineNum + 2);
|
|
26
|
+
const context = lines.slice(start, end).map((l, i) => {
|
|
27
|
+
const num = start + i + 1;
|
|
28
|
+
const marker = num === lineNum ? ' >> ' : ' ';
|
|
29
|
+
return `${marker}${num} | ${l}`;
|
|
30
|
+
}).join('\n');
|
|
31
|
+
e.message += `\n\n${context}\n`;
|
|
32
|
+
}
|
|
33
|
+
throw e;
|
|
9
34
|
}
|
|
10
|
-
const lexer = new Lexer(source);
|
|
11
|
-
const tokens = lexer.tokenize();
|
|
12
|
-
const parser = new Parser(tokens);
|
|
13
|
-
const ast = parser.parse();
|
|
14
|
-
const generator = new Generator({ runtimePath });
|
|
15
|
-
const js = generator.generate(ast);
|
|
16
|
-
return { js, ast, tokens, naide: mode === 'x' ? source : null };
|
|
17
35
|
}
|
|
18
36
|
|
|
19
37
|
export function transpile(source, opts) {
|
package/src/lexer.js
CHANGED
|
@@ -11,7 +11,7 @@ class Token {
|
|
|
11
11
|
|
|
12
12
|
export class Lexer {
|
|
13
13
|
constructor(source) {
|
|
14
|
-
this.source = source;
|
|
14
|
+
this.source = source.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
15
15
|
this.pos = 0;
|
|
16
16
|
this.line = 1;
|
|
17
17
|
this.col = 1;
|
|
@@ -78,6 +78,11 @@ export class Lexer {
|
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
if (ch === ';') {
|
|
82
|
+
this.advance();
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
if (ch === '"' || ch === "'") {
|
|
82
87
|
this.readString(ch);
|
|
83
88
|
continue;
|
|
@@ -195,7 +200,11 @@ export class Lexer {
|
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
202
|
|
|
198
|
-
if (this.peek() === quote)
|
|
203
|
+
if (this.peek() === quote) {
|
|
204
|
+
this.advance();
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error(`[NAIDE Lexer Error] Unterminated string starting at line ${startLine}:${startCol}`);
|
|
207
|
+
}
|
|
199
208
|
|
|
200
209
|
if (current) parts.push({ type: 'text', value: current });
|
|
201
210
|
|
|
@@ -221,7 +230,11 @@ export class Lexer {
|
|
|
221
230
|
value += this.advance();
|
|
222
231
|
}
|
|
223
232
|
}
|
|
224
|
-
if (this.peek() === '`')
|
|
233
|
+
if (this.peek() === '`') {
|
|
234
|
+
this.advance();
|
|
235
|
+
} else {
|
|
236
|
+
throw new Error(`[NAIDE Lexer Error] Unterminated template string starting at line ${startLine}:${startCol}`);
|
|
237
|
+
}
|
|
225
238
|
this.tokens.push(new Token(T.STRING, { parts: [{ type: 'text', value }], raw: value }, startLine, startCol));
|
|
226
239
|
}
|
|
227
240
|
|
|
@@ -371,7 +384,7 @@ export class Lexer {
|
|
|
371
384
|
case ':': this.tokens.push(new Token(T.COLON, ':', startLine, startCol)); break;
|
|
372
385
|
case ',': this.tokens.push(new Token(T.COMMA, ',', startLine, startCol)); break;
|
|
373
386
|
default:
|
|
374
|
-
throw new Error(`Unexpected character '${ch}' at line ${startLine}:${startCol}`);
|
|
387
|
+
throw new Error(`[NAIDE Lexer Error] Unexpected character '${ch}' at line ${startLine}:${startCol}`);
|
|
375
388
|
}
|
|
376
389
|
}
|
|
377
390
|
|
package/src/parser.js
CHANGED
|
@@ -61,7 +61,16 @@ export class Parser {
|
|
|
61
61
|
expect(type) {
|
|
62
62
|
const tok = this.advance();
|
|
63
63
|
if (tok.type !== type) {
|
|
64
|
-
|
|
64
|
+
const hints = {
|
|
65
|
+
COLON: "Missing ':' — blocks (if, fn, server, etc.) need a colon at the end",
|
|
66
|
+
INDENT: "Expected an indented block — check your indentation (use 2 spaces)",
|
|
67
|
+
RPAREN: "Missing closing ')'",
|
|
68
|
+
RBRACKET: "Missing closing ']'",
|
|
69
|
+
RBRACE: "Missing closing '}'",
|
|
70
|
+
IDENT: "Expected an identifier (variable/function name)",
|
|
71
|
+
};
|
|
72
|
+
const hint = hints[type] ? `\n Hint: ${hints[type]}` : '';
|
|
73
|
+
throw this.error(`Expected ${type} but got ${tok.type} ('${tok.value}')${hint}`, tok);
|
|
65
74
|
}
|
|
66
75
|
return tok;
|
|
67
76
|
}
|
|
@@ -81,18 +90,24 @@ export class Parser {
|
|
|
81
90
|
return types.includes(this.peek().type);
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
isIdentLike(type) {
|
|
94
|
+
return type === T.IDENT || TYPE_TOKENS.has(type) ||
|
|
95
|
+
type === T.LOG || type === T.DB || type === T.GET ||
|
|
96
|
+
type === T.POST || type === T.PUT || type === T.DEL ||
|
|
97
|
+
type === T.MATCH || type === T.ON || type === T.NEW ||
|
|
98
|
+
type === T.FROM || type === T.AS || type === T.SELF ||
|
|
99
|
+
type === T.SCHEMA || type === T.CRUD || type === T.AUTH ||
|
|
100
|
+
type === T.CORS || type === T.LIMIT || type === T.ENV ||
|
|
101
|
+
type === T.EVERY || type === T.WATCH || type === T.STATIC ||
|
|
102
|
+
type === T.WS || type === T.GROUP || type === T.COOKIE ||
|
|
103
|
+
type === T.NOT || type === T.AND || type === T.OR ||
|
|
104
|
+
type === T.IN || type === T.BREAK || type === T.CONTINUE ||
|
|
105
|
+
type === T.THROW || type === T.MUT || type === T.PUB;
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
expectPropertyName() {
|
|
85
109
|
const tok = this.advance();
|
|
86
|
-
if (
|
|
87
|
-
tok.type === T.LOG || tok.type === T.DB || tok.type === T.GET ||
|
|
88
|
-
tok.type === T.POST || tok.type === T.PUT || tok.type === T.DEL ||
|
|
89
|
-
tok.type === T.MATCH || tok.type === T.ON || tok.type === T.NEW ||
|
|
90
|
-
tok.type === T.FROM || tok.type === T.AS || tok.type === T.SELF ||
|
|
91
|
-
tok.type === T.SCHEMA || tok.type === T.CRUD || tok.type === T.AUTH ||
|
|
92
|
-
tok.type === T.CORS || tok.type === T.LIMIT || tok.type === T.ENV ||
|
|
93
|
-
tok.type === T.EVERY || tok.type === T.WATCH ||
|
|
94
|
-
tok.type === T.STATIC || tok.type === T.WS ||
|
|
95
|
-
tok.type === T.GROUP || tok.type === T.COOKIE) {
|
|
110
|
+
if (this.isIdentLike(tok.type)) {
|
|
96
111
|
return tok.value;
|
|
97
112
|
}
|
|
98
113
|
throw this.error(`Expected property name but got ${tok.type} ('${tok.value}')`, tok);
|
|
@@ -726,6 +741,18 @@ export class Parser {
|
|
|
726
741
|
}
|
|
727
742
|
|
|
728
743
|
parseTernary() {
|
|
744
|
+
if (this.at(T.IF)) {
|
|
745
|
+
const savedPos = this.pos;
|
|
746
|
+
this.advance();
|
|
747
|
+
const condition = this.parseNullish();
|
|
748
|
+
if (this.match(T.THEN)) {
|
|
749
|
+
const consequent = this.parseNullish();
|
|
750
|
+
this.expect(T.ELSE);
|
|
751
|
+
const alternate = this.parseNullish();
|
|
752
|
+
return new ASTNode('Ternary', { condition, consequent, alternate });
|
|
753
|
+
}
|
|
754
|
+
this.pos = savedPos;
|
|
755
|
+
}
|
|
729
756
|
let expr = this.parseNullish();
|
|
730
757
|
if (this.at(T.IF)) {
|
|
731
758
|
const savedPos = this.pos;
|
|
@@ -906,6 +933,7 @@ export class Parser {
|
|
|
906
933
|
case T.SCHEMA: case T.CRUD: case T.AUTH: case T.CORS:
|
|
907
934
|
case T.LIMIT: case T.ENV: case T.EVERY: case T.WATCH:
|
|
908
935
|
case T.STATIC: case T.WS: case T.GROUP: case T.COOKIE:
|
|
936
|
+
case T.FROM: case T.AS: case T.IN:
|
|
909
937
|
this.advance();
|
|
910
938
|
return new ASTNode('Identifier', { name: tok.value });
|
|
911
939
|
|