naidejs 1.1.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/README.md +183 -4
- package/SPEC-X.nx +80 -7
- package/SPEC.naide +117 -9
- package/bin/naide.js +55 -1
- package/examples/fullapp.naide +41 -4
- package/examples/fullapp.nx +41 -4
- package/package.json +9 -5
- package/src/generator.js +248 -5
- package/src/index.js +26 -8
- package/src/lexer.js +17 -4
- package/src/parser.js +179 -98
- package/src/preprocess.js +8 -0
- package/src/runtime.js +162 -4
- package/src/tokens.js +8 -0
package/examples/fullapp.nx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
-- Full NAIDE-X app with
|
|
1
|
+
-- Full NAIDE-X app with all features
|
|
2
|
+
|
|
3
|
+
db "data/"
|
|
2
4
|
|
|
3
5
|
env:
|
|
4
6
|
PORT int default(3000)
|
|
@@ -8,6 +10,7 @@ schema User:
|
|
|
8
10
|
id auto
|
|
9
11
|
name str required min(2) max(50)
|
|
10
12
|
email str required email
|
|
13
|
+
password str required
|
|
11
14
|
role str default("user")
|
|
12
15
|
|
|
13
16
|
schema Todo:
|
|
@@ -18,17 +21,51 @@ schema Todo:
|
|
|
18
21
|
|
|
19
22
|
$app:PORT
|
|
20
23
|
cors "*"
|
|
24
|
+
cookie
|
|
21
25
|
auth JWT_SECRET:
|
|
22
26
|
protect "/api/*"
|
|
23
27
|
public "/api/auth/*"
|
|
24
28
|
limit "/api/*" 100 "1m"
|
|
29
|
+
static "/public"
|
|
25
30
|
crud "/api/users" User
|
|
26
31
|
crud "/api/todos" Todo
|
|
32
|
+
|
|
33
|
+
group "/api/v1":
|
|
34
|
+
G"/status"
|
|
35
|
+
>{version:"1.0",status:"ok"}
|
|
36
|
+
|
|
37
|
+
P"/api/auth/register"(req,res)
|
|
38
|
+
s:hashed=hash(req.body.password)
|
|
39
|
+
user=UserStore.create({name:req.body.name,email:req.body.email,password:hashed})
|
|
40
|
+
?user.error
|
|
41
|
+
>.s 400 {errors:user.error}
|
|
42
|
+
token=auth.sign({id:user.id,role:user.role})
|
|
43
|
+
>{token,user:{id:user.id,name:user.name,email:user.email}}
|
|
44
|
+
|
|
45
|
+
P"/api/auth/login"(req,res)
|
|
46
|
+
user=UserStore.where({email:req.body.email})[0]
|
|
47
|
+
?not user
|
|
48
|
+
>.s 401 {error:"Invalid credentials"}
|
|
49
|
+
?not verify(req.body.password,user.password)
|
|
50
|
+
>.s 401 {error:"Invalid credentials"}
|
|
51
|
+
token=auth.sign({id:user.id,role:user.role})
|
|
52
|
+
>{token}
|
|
53
|
+
|
|
27
54
|
G"/health"
|
|
28
55
|
>{status:"ok"}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
>
|
|
56
|
+
|
|
57
|
+
G"/"
|
|
58
|
+
>.h "<h1>Welcome to NAIDE</h1>"
|
|
59
|
+
|
|
60
|
+
ws "/chat":
|
|
61
|
+
on "connect":
|
|
62
|
+
send({type:"welcome"})
|
|
63
|
+
on "message" (data):
|
|
64
|
+
broadcast(data)
|
|
65
|
+
|
|
66
|
+
error (err, req, res):
|
|
67
|
+
log.error err.message
|
|
68
|
+
>.s 500 {error:"Internal server error"}
|
|
32
69
|
|
|
33
70
|
every "30m":
|
|
34
71
|
log"cleanup running"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naidejs",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "NAIDE - Node AI Development Environment.
|
|
3
|
+
"version": "1.3.0",
|
|
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": {
|
|
7
7
|
".": "./src/index.js",
|
|
@@ -35,15 +35,19 @@
|
|
|
35
35
|
"claude",
|
|
36
36
|
"chatgpt",
|
|
37
37
|
"naide",
|
|
38
|
-
"dsl"
|
|
38
|
+
"dsl",
|
|
39
|
+
"express",
|
|
40
|
+
"api",
|
|
41
|
+
"backend",
|
|
42
|
+
"server"
|
|
39
43
|
],
|
|
40
44
|
"author": "pirikari.sena@gmail.com",
|
|
41
45
|
"license": "MIT",
|
|
42
46
|
"repository": {
|
|
43
47
|
"type": "git",
|
|
44
|
-
"url": "https://github.com/
|
|
48
|
+
"url": "https://github.com/irxk/naide"
|
|
45
49
|
},
|
|
46
|
-
"homepage": "https://github.com/
|
|
50
|
+
"homepage": "https://github.com/irxk/naide#readme",
|
|
47
51
|
"engines": {
|
|
48
52
|
"node": ">=18.0.0"
|
|
49
53
|
}
|
package/src/generator.js
CHANGED
|
@@ -8,6 +8,10 @@ export class Generator {
|
|
|
8
8
|
this.schemas = new Map();
|
|
9
9
|
this.needsEventBus = false;
|
|
10
10
|
this.runtimePath = options.runtimePath || 'naidejs/runtime';
|
|
11
|
+
this.dbDir = null;
|
|
12
|
+
this.authSecret = null;
|
|
13
|
+
this.hasWs = false;
|
|
14
|
+
this.wsNodes = [];
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
generate(ast) {
|
|
@@ -58,6 +62,7 @@ export class Generator {
|
|
|
58
62
|
case 'Function': return this.visitFunction(node);
|
|
59
63
|
case 'Return': return this.visitReturn(node);
|
|
60
64
|
case 'ReturnStatus': return this.visitReturnStatus(node);
|
|
65
|
+
case 'ReturnMethod': return this.visitReturnMethod(node);
|
|
61
66
|
case 'TypedVar': return this.visitTypedVar(node);
|
|
62
67
|
case 'If': return this.visitIf(node);
|
|
63
68
|
case 'Each': return this.visitEach(node);
|
|
@@ -76,6 +81,7 @@ export class Generator {
|
|
|
76
81
|
case 'CompoundAssign': return this.visitCompoundAssign(node);
|
|
77
82
|
case 'ExprStatement': this.emit(this.expr(node.expression) + ';'); return;
|
|
78
83
|
case 'DbConnect': return this.visitDbConnect(node);
|
|
84
|
+
case 'DbDir': return this.visitDbDir(node);
|
|
79
85
|
case 'AwaitAll': return this.visitAwaitAllStatement(node);
|
|
80
86
|
case 'SchemaDecl': return this.visitSchema(node);
|
|
81
87
|
case 'CrudDecl': return this.visitCrudTopLevel(node);
|
|
@@ -85,6 +91,11 @@ export class Generator {
|
|
|
85
91
|
case 'EnvDecl': return this.visitEnv(node);
|
|
86
92
|
case 'EveryDecl': return this.visitEvery(node);
|
|
87
93
|
case 'WatchDecl': return this.visitWatch(node);
|
|
94
|
+
case 'StaticDecl': return this.visitStaticTopLevel(node);
|
|
95
|
+
case 'WsDecl': return this.visitWsTopLevel(node);
|
|
96
|
+
case 'GroupDecl': return this.visitGroupTopLevel(node);
|
|
97
|
+
case 'ErrorHandler': return this.visitErrorHandlerTopLevel(node);
|
|
98
|
+
case 'CookieDecl': return this.visitCookieTopLevel(node);
|
|
88
99
|
default:
|
|
89
100
|
this.emit(`/* unknown: ${node.type} */`);
|
|
90
101
|
}
|
|
@@ -135,6 +146,26 @@ export class Generator {
|
|
|
135
146
|
this.emit(`return res.status(${this.expr(node.statusCode)}).json(${this.expr(node.body)});`);
|
|
136
147
|
}
|
|
137
148
|
|
|
149
|
+
visitReturnMethod(node) {
|
|
150
|
+
const val = this.expr(node.value);
|
|
151
|
+
switch (node.method) {
|
|
152
|
+
case 'redirect':
|
|
153
|
+
this.emit(`return res.redirect(${val});`);
|
|
154
|
+
break;
|
|
155
|
+
case 'html':
|
|
156
|
+
this.emit(`return res.type('html').send(${val});`);
|
|
157
|
+
break;
|
|
158
|
+
case 'text':
|
|
159
|
+
this.emit(`return res.type('text').send(${val});`);
|
|
160
|
+
break;
|
|
161
|
+
case 'file':
|
|
162
|
+
this.emit(`return res.sendFile(${val});`);
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
this.emit(`return res.${node.method}(${val});`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
138
169
|
visitTypedVar(node) {
|
|
139
170
|
const keyword = node.isMut ? 'let' : 'const';
|
|
140
171
|
const exp = node.isPublic ? 'export ' : '';
|
|
@@ -252,10 +283,16 @@ export class Generator {
|
|
|
252
283
|
}
|
|
253
284
|
|
|
254
285
|
visitServer(node) {
|
|
286
|
+
const hasWs = node.routes.some(r => r.type === 'WsDecl');
|
|
287
|
+
|
|
255
288
|
this.emit(`import express from 'express';`);
|
|
289
|
+
if (hasWs) {
|
|
290
|
+
this.emit(`import { WebSocketServer } from 'ws';`);
|
|
291
|
+
}
|
|
256
292
|
this.emitRaw('');
|
|
257
293
|
this.emit(`const ${node.name} = express();`);
|
|
258
294
|
this.emit(`${node.name}.use(express.json());`);
|
|
295
|
+
this.emit(`${node.name}.use(express.urlencoded({ extended: true }));`);
|
|
259
296
|
this.emitRaw('');
|
|
260
297
|
|
|
261
298
|
for (const mid of node.middleware) {
|
|
@@ -263,6 +300,9 @@ export class Generator {
|
|
|
263
300
|
this.emitRaw('');
|
|
264
301
|
}
|
|
265
302
|
|
|
303
|
+
const wsNodes = [];
|
|
304
|
+
const errorHandlers = [];
|
|
305
|
+
|
|
266
306
|
for (const child of node.routes) {
|
|
267
307
|
if (child.type === 'Route') {
|
|
268
308
|
this.visitRoute(node.name, child);
|
|
@@ -274,18 +314,41 @@ export class Generator {
|
|
|
274
314
|
this.visitCors(node.name, child);
|
|
275
315
|
} else if (child.type === 'LimitDecl') {
|
|
276
316
|
this.visitLimit(node.name, child);
|
|
317
|
+
} else if (child.type === 'StaticDecl') {
|
|
318
|
+
this.visitStatic(node.name, child);
|
|
319
|
+
} else if (child.type === 'WsDecl') {
|
|
320
|
+
wsNodes.push(child);
|
|
321
|
+
} else if (child.type === 'GroupDecl') {
|
|
322
|
+
this.visitGroup(node.name, child);
|
|
323
|
+
} else if (child.type === 'ErrorHandler') {
|
|
324
|
+
errorHandlers.push(child);
|
|
325
|
+
} else if (child.type === 'CookieDecl') {
|
|
326
|
+
this.visitCookie(node.name, child);
|
|
277
327
|
} else {
|
|
278
328
|
this.visitStatement(child);
|
|
279
329
|
}
|
|
280
330
|
}
|
|
281
331
|
|
|
332
|
+
for (const eh of errorHandlers) {
|
|
333
|
+
this.visitErrorHandler(node.name, eh);
|
|
334
|
+
}
|
|
335
|
+
|
|
282
336
|
const port = node.port ? this.expr(node.port) : '3000';
|
|
283
337
|
this.emitRaw('');
|
|
284
|
-
|
|
338
|
+
if (hasWs) {
|
|
339
|
+
this.emit(`const __server = ${node.name}.listen(${port}, () => {`);
|
|
340
|
+
} else {
|
|
341
|
+
this.emit(`${node.name}.listen(${port}, () => {`);
|
|
342
|
+
}
|
|
285
343
|
this.indent++;
|
|
286
344
|
this.emit(`console.log(\`Server running on port \${${port}}\`);`);
|
|
287
345
|
this.indent--;
|
|
288
346
|
this.emit('});');
|
|
347
|
+
|
|
348
|
+
for (const wsNode of wsNodes) {
|
|
349
|
+
this.emitRaw('');
|
|
350
|
+
this.visitWs(wsNode);
|
|
351
|
+
}
|
|
289
352
|
}
|
|
290
353
|
|
|
291
354
|
visitRoute(appName, route) {
|
|
@@ -419,16 +482,25 @@ export class Generator {
|
|
|
419
482
|
this.emit(`const db = new Database(${this.expr(node.connectionString)});`);
|
|
420
483
|
}
|
|
421
484
|
|
|
485
|
+
visitDbDir(node) {
|
|
486
|
+
const raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'data/';
|
|
487
|
+
this.dbDir = raw.endsWith('/') ? raw : raw + '/';
|
|
488
|
+
}
|
|
489
|
+
|
|
422
490
|
visitAwaitAllStatement(node) {
|
|
423
491
|
const exprs = node.expressions.map(e => this.expr(e)).join(', ');
|
|
424
492
|
this.emit(`await Promise.all([${exprs}]);`);
|
|
425
493
|
}
|
|
426
494
|
|
|
427
|
-
// =====
|
|
495
|
+
// ===== High-level features =====
|
|
428
496
|
|
|
429
497
|
visitSchema(node) {
|
|
430
498
|
this.runtimeImports.add('createSchema');
|
|
431
|
-
this.
|
|
499
|
+
if (this.dbDir) {
|
|
500
|
+
this.runtimeImports.add('createFileStore');
|
|
501
|
+
} else {
|
|
502
|
+
this.runtimeImports.add('createStore');
|
|
503
|
+
}
|
|
432
504
|
|
|
433
505
|
this.emit(`const ${node.name}Schema = createSchema('${node.name}', {`);
|
|
434
506
|
this.indent++;
|
|
@@ -487,7 +559,11 @@ export class Generator {
|
|
|
487
559
|
|
|
488
560
|
this.indent--;
|
|
489
561
|
this.emit('});');
|
|
490
|
-
this.
|
|
562
|
+
if (this.dbDir) {
|
|
563
|
+
this.emit(`const ${node.name}Store = createFileStore(${node.name}Schema, '${this.dbDir}${node.name}.json');`);
|
|
564
|
+
} else {
|
|
565
|
+
this.emit(`const ${node.name}Store = createStore(${node.name}Schema);`);
|
|
566
|
+
}
|
|
491
567
|
this.emitRaw('');
|
|
492
568
|
|
|
493
569
|
this.schemas.set(node.name, node);
|
|
@@ -510,6 +586,10 @@ export class Generator {
|
|
|
510
586
|
this.runtimeImports.add('jwtAuth');
|
|
511
587
|
|
|
512
588
|
const secret = this.expr(node.secret);
|
|
589
|
+
this.authSecret = secret;
|
|
590
|
+
|
|
591
|
+
this.emit(`const __authSecret = ${secret};`);
|
|
592
|
+
|
|
513
593
|
const options = [];
|
|
514
594
|
if (node.publicPaths.length > 0) {
|
|
515
595
|
const paths = node.publicPaths.map(p => this.stringValue(p)).join(', ');
|
|
@@ -560,6 +640,125 @@ export class Generator {
|
|
|
560
640
|
this.visitLimit('app', node);
|
|
561
641
|
}
|
|
562
642
|
|
|
643
|
+
visitStatic(appName, node) {
|
|
644
|
+
let raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'public';
|
|
645
|
+
if (raw.startsWith('/')) raw = raw.slice(1);
|
|
646
|
+
this.emit(`${appName}.use(express.static(${JSON.stringify(raw)}));`);
|
|
647
|
+
this.emitRaw('');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
visitStaticTopLevel(node) {
|
|
651
|
+
this.visitStatic('app', node);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
visitWs(node) {
|
|
655
|
+
const path = this.stringValue(node.path);
|
|
656
|
+
this.emit(`const __wss = new WebSocketServer({ server: __server, path: ${path} });`);
|
|
657
|
+
this.emit(`__wss.on('connection', (__ws) => {`);
|
|
658
|
+
this.indent++;
|
|
659
|
+
this.emit(`const send = (d) => __ws.send(typeof d === 'string' ? d : JSON.stringify(d));`);
|
|
660
|
+
this.emit(`const broadcast = (d) => { const m = typeof d === 'string' ? d : JSON.stringify(d); for (const c of __wss.clients) if (c.readyState === 1) c.send(m); };`);
|
|
661
|
+
|
|
662
|
+
const connectEvents = node.events.filter(e => {
|
|
663
|
+
const name = e.name.raw || e.name.parts?.map(p => p.value).join('');
|
|
664
|
+
return name === 'connect' || name === 'open';
|
|
665
|
+
});
|
|
666
|
+
const otherEvents = node.events.filter(e => {
|
|
667
|
+
const name = e.name.raw || e.name.parts?.map(p => p.value).join('');
|
|
668
|
+
return name !== 'connect' && name !== 'open';
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
for (const evt of connectEvents) {
|
|
672
|
+
this.emitRaw('');
|
|
673
|
+
for (const stmt of evt.body) this.visitStatement(stmt);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
for (const evt of otherEvents) {
|
|
677
|
+
const evtName = evt.name.raw || evt.name.parts?.map(p => p.value).join('');
|
|
678
|
+
this.emitRaw('');
|
|
679
|
+
|
|
680
|
+
if (evtName === 'message') {
|
|
681
|
+
const needsAsync = this.bodyUsesAwait(evt.body);
|
|
682
|
+
const asyncPrefix = needsAsync ? 'async ' : '';
|
|
683
|
+
this.emit(`__ws.on('message', ${asyncPrefix}(__raw) => {`);
|
|
684
|
+
this.indent++;
|
|
685
|
+
const dataParam = evt.params[0] || 'data';
|
|
686
|
+
this.emit(`const ${dataParam} = JSON.parse(__raw);`);
|
|
687
|
+
for (const stmt of evt.body) this.visitStatement(stmt);
|
|
688
|
+
this.indent--;
|
|
689
|
+
this.emit('});');
|
|
690
|
+
} else {
|
|
691
|
+
const params = evt.params.length > 0 ? evt.params.join(', ') : '';
|
|
692
|
+
const needsAsync = this.bodyUsesAwait(evt.body);
|
|
693
|
+
const asyncPrefix = needsAsync ? 'async ' : '';
|
|
694
|
+
this.emit(`__ws.on('${evtName}', ${asyncPrefix}(${params}) => {`);
|
|
695
|
+
this.indent++;
|
|
696
|
+
for (const stmt of evt.body) this.visitStatement(stmt);
|
|
697
|
+
this.indent--;
|
|
698
|
+
this.emit('});');
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
this.indent--;
|
|
703
|
+
this.emit('});');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
visitWsTopLevel(node) {
|
|
707
|
+
this.visitWs(node);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
visitGroup(appName, node) {
|
|
711
|
+
const prefix = this.stringValue(node.prefix);
|
|
712
|
+
this.groupCounter = (this.groupCounter || 0) + 1;
|
|
713
|
+
const routerName = `__router${this.groupCounter}`;
|
|
714
|
+
this.emit(`const ${routerName} = express.Router();`);
|
|
715
|
+
|
|
716
|
+
for (const child of node.routes) {
|
|
717
|
+
if (child.type === 'Route') {
|
|
718
|
+
this.visitRoute(routerName, child);
|
|
719
|
+
} else if (child.type === 'CrudDecl') {
|
|
720
|
+
this.visitCrud(routerName, child);
|
|
721
|
+
} else if (child.type === 'AuthDecl') {
|
|
722
|
+
this.visitAuth(routerName, child);
|
|
723
|
+
} else if (child.type === 'GroupDecl') {
|
|
724
|
+
this.visitGroup(routerName, child);
|
|
725
|
+
} else {
|
|
726
|
+
this.visitStatement(child);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
this.emit(`${appName}.use(${prefix}, ${routerName});`);
|
|
731
|
+
this.emitRaw('');
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
visitGroupTopLevel(node) {
|
|
735
|
+
this.visitGroup('app', node);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
visitErrorHandler(appName, node) {
|
|
739
|
+
const params = node.params.join(', ');
|
|
740
|
+
this.emit(`${appName}.use((${params}, next) => {`);
|
|
741
|
+
this.indent++;
|
|
742
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
743
|
+
this.indent--;
|
|
744
|
+
this.emit('});');
|
|
745
|
+
this.emitRaw('');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
visitErrorHandlerTopLevel(node) {
|
|
749
|
+
this.visitErrorHandler('app', node);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
visitCookie(appName, node) {
|
|
753
|
+
this.runtimeImports.add('cookieParser');
|
|
754
|
+
this.emit(`${appName}.use(cookieParser());`);
|
|
755
|
+
this.emitRaw('');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
visitCookieTopLevel(node) {
|
|
759
|
+
this.visitCookie('app', node);
|
|
760
|
+
}
|
|
761
|
+
|
|
563
762
|
visitEnv(node) {
|
|
564
763
|
this.runtimeImports.add('loadEnv');
|
|
565
764
|
|
|
@@ -661,7 +860,7 @@ export class Generator {
|
|
|
661
860
|
return `${this.expr(node.object)}?.${node.property}`;
|
|
662
861
|
|
|
663
862
|
case 'Call':
|
|
664
|
-
return
|
|
863
|
+
return this.generateCall(node);
|
|
665
864
|
|
|
666
865
|
case 'IndexAccess':
|
|
667
866
|
return `${this.expr(node.object)}[${this.expr(node.index)}]`;
|
|
@@ -714,6 +913,50 @@ export class Generator {
|
|
|
714
913
|
}
|
|
715
914
|
}
|
|
716
915
|
|
|
916
|
+
generateCall(node) {
|
|
917
|
+
const AUTO_IMPORT = { 'hash': 'hash', 'verify': 'verify', 'uuid': 'uuid' };
|
|
918
|
+
|
|
919
|
+
if (node.callee.type === 'Identifier' && AUTO_IMPORT[node.callee.name]) {
|
|
920
|
+
const runtimeFn = AUTO_IMPORT[node.callee.name];
|
|
921
|
+
this.runtimeImports.add(runtimeFn);
|
|
922
|
+
return `${runtimeFn}(${node.args.map(a => this.expr(a)).join(', ')})`;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'sign') {
|
|
926
|
+
this.runtimeImports.add('jwtSign');
|
|
927
|
+
const args = node.args.map(a => this.expr(a));
|
|
928
|
+
if (args.length === 1 && this.authSecret) {
|
|
929
|
+
return `jwtSign(${args[0]}, __authSecret)`;
|
|
930
|
+
}
|
|
931
|
+
return `jwtSign(${args.join(', ')})`;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (node.callee.type === 'MemberAccess' &&
|
|
935
|
+
node.callee.object.type === 'Identifier' &&
|
|
936
|
+
node.callee.object.name === 'api') {
|
|
937
|
+
this.runtimeImports.add('api');
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (node.callee.type === 'MemberAccess' &&
|
|
941
|
+
node.callee.object.type === 'Identifier' &&
|
|
942
|
+
node.callee.object.name === 'auth') {
|
|
943
|
+
if (node.callee.property === 'sign') {
|
|
944
|
+
this.runtimeImports.add('jwtSign');
|
|
945
|
+
const args = node.args.map(a => this.expr(a));
|
|
946
|
+
if (args.length === 1 && this.authSecret) {
|
|
947
|
+
return `jwtSign(${args[0]}, __authSecret)`;
|
|
948
|
+
}
|
|
949
|
+
return `jwtSign(${args.join(', ')})`;
|
|
950
|
+
}
|
|
951
|
+
if (node.callee.property === 'verify') {
|
|
952
|
+
this.runtimeImports.add('jwtVerify');
|
|
953
|
+
return `jwtVerify(${node.args.map(a => this.expr(a)).join(', ')})`;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return `${this.expr(node.callee)}(${node.args.map(a => this.expr(a)).join(', ')})`;
|
|
958
|
+
}
|
|
959
|
+
|
|
717
960
|
generateString(strData) {
|
|
718
961
|
if (!strData || !strData.parts) return '""';
|
|
719
962
|
|
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
|
|