naider 1.14.0 → 1.16.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.
@@ -0,0 +1,1569 @@
1
+ export class DartGenerator {
2
+ constructor(options = {}) {
3
+ this.indent = 0;
4
+ this.output = [];
5
+ this.models = new Map();
6
+ this.schemas = new Map();
7
+ this.imports = new Set();
8
+ this.sourceMap = [];
9
+ this.currentSourceLine = 0;
10
+ this.sourceFile = options.sourceFile || null;
11
+ this.authSecret = null;
12
+ this.dbVar = null;
13
+ this.usesServer = false;
14
+ this.hasTests = false;
15
+ this.hasAsserts = false;
16
+ this.mainBody = [];
17
+ this.topLevel = [];
18
+ this.inFunction = false;
19
+ this.inClass = false;
20
+ }
21
+
22
+ generate(ast) {
23
+ this.visitProgram(ast);
24
+
25
+ const preamble = [];
26
+
27
+ for (const imp of [...this.imports].sort()) {
28
+ preamble.push(imp);
29
+ }
30
+
31
+ if (preamble.length > 0) {
32
+ preamble.push('');
33
+ }
34
+
35
+ const result = [];
36
+ if (preamble.length > 0) result.push(...preamble);
37
+ if (this.topLevel.length > 0) {
38
+ result.push(...this.topLevel);
39
+ result.push('');
40
+ }
41
+ result.push('void main() async {');
42
+ result.push(...this.mainBody);
43
+ result.push('}');
44
+
45
+ return result.join('\n');
46
+ }
47
+
48
+ emit(line) {
49
+ const indented = ' '.repeat(this.indent) + line;
50
+ if (this.inFunction || this.inClass) {
51
+ this.mainBody.push(indented);
52
+ } else {
53
+ this.mainBody.push(' ' + indented);
54
+ }
55
+ this.sourceMap.push(this.currentSourceLine);
56
+ }
57
+
58
+ emitRaw(line) {
59
+ if (this.inFunction || this.inClass) {
60
+ this.mainBody.push(line);
61
+ } else {
62
+ this.mainBody.push(line);
63
+ }
64
+ this.sourceMap.push(this.currentSourceLine);
65
+ }
66
+
67
+ emitTop(line) {
68
+ this.topLevel.push(line);
69
+ this.sourceMap.push(this.currentSourceLine);
70
+ }
71
+
72
+ emitTopRaw(line) {
73
+ this.topLevel.push(line);
74
+ this.sourceMap.push(this.currentSourceLine);
75
+ }
76
+
77
+ addImport(imp) {
78
+ this.imports.add(imp);
79
+ }
80
+
81
+ visitProgram(node) {
82
+ for (const stmt of node.body) {
83
+ this.visitStatement(stmt);
84
+ }
85
+ }
86
+
87
+ visitStatement(node) {
88
+ if (node._line) this.currentSourceLine = node._line;
89
+ switch (node.type) {
90
+ case 'Use': return this.visitUse(node);
91
+ case 'UseDestructured': return this.visitUseDestructured(node);
92
+ case 'Function': return this.visitFunction(node);
93
+ case 'Return': return this.visitReturn(node);
94
+ case 'ReturnStatus': return this.visitReturnStatus(node);
95
+ case 'ReturnMethod': return this.visitReturnMethod(node);
96
+ case 'TypedVar': return this.visitTypedVar(node);
97
+ case 'If': return this.visitIf(node);
98
+ case 'Each': return this.visitEach(node);
99
+ case 'For': return this.visitFor(node);
100
+ case 'While': return this.visitWhile(node);
101
+ case 'Match': return this.visitMatch(node);
102
+ case 'Try': return this.visitTry(node);
103
+ case 'Server': return this.visitServer(node);
104
+ case 'Bot': return this.visitBot(node);
105
+ case 'Model': return this.visitModel(node);
106
+ case 'On': return this.visitOn(node);
107
+ case 'Log': return this.visitLog(node);
108
+ case 'Throw': return this.visitThrow(node);
109
+ case 'Break': this.emit('break;'); return;
110
+ case 'Continue': this.emit('continue;'); return;
111
+ case 'Assignment': return this.visitAssignment(node);
112
+ case 'CompoundAssign': return this.visitCompoundAssign(node);
113
+ case 'ExprStatement': this.emit(this.expr(node.expression) + ';'); return;
114
+ case 'DbConnect': return this.visitDbConnect(node);
115
+ case 'DbDir': return this.visitDbDir(node);
116
+ case 'DbSql': return this.visitDbSql(node);
117
+ case 'AwaitAll': return this.visitAwaitAllStatement(node);
118
+ case 'SchemaDecl': return this.visitSchema(node);
119
+ case 'CrudDecl': return this.visitCrud('app', node);
120
+ case 'AuthDecl': return this.visitAuth('app', node);
121
+ case 'CorsDecl': return this.visitCors('app', node);
122
+ case 'LimitDecl': return this.visitLimit('app', node);
123
+ case 'EnvDecl': return this.visitEnv(node);
124
+ case 'EveryDecl': return this.visitEvery(node);
125
+ case 'WatchDecl': return this.visitWatch(node);
126
+ case 'StaticDecl': return this.visitStatic('app', node);
127
+ case 'WsDecl': return this.visitWs(node);
128
+ case 'GroupDecl': return this.visitGroup('app', node);
129
+ case 'ErrorHandler': return this.visitErrorHandler('app', node);
130
+ case 'CookieDecl': return this.visitCookie('app', node);
131
+ case 'UploadDecl': return this.visitUpload('app', node);
132
+ case 'SessionDecl': return this.visitSession('app', node);
133
+ case 'ViewDecl': return this.visitView('app', node);
134
+ case 'SseDecl': return this.visitSse('app', node);
135
+ case 'CacheDecl': return this.visitCache('app', node);
136
+ case 'MiddlewareRef': return this.visitMiddlewareRef('app', node);
137
+ case 'ReturnRender': return this.visitReturnRender(node);
138
+ case 'ValidateDecl': return this.visitValidate('app', node);
139
+ case 'TestDecl': return this.visitTest(node);
140
+ case 'AssertStmt': return this.visitAssert(node);
141
+ case 'QueueDecl': return this.visitQueue(node);
142
+ case 'OpenapiDecl': return this.visitOpenapi('app', node);
143
+ case 'ReturnRedirect': return this.visitReturnRedirect(node);
144
+ case 'ReturnDownload': return this.visitReturnDownload(node);
145
+ case 'PromptDecl': return this.visitPrompt(node);
146
+ case 'Page': return this.visitPage(node);
147
+ case 'CliApp': return this.visitCli(node);
148
+ case 'MailConfig': return this.visitMail(node);
149
+ case 'DesktopApp': return this.visitDesktop(node);
150
+ case 'Screen': return this.visitScreen(node);
151
+ case 'OauthDecl': return this.visitOauth(node);
152
+ case 'PayDecl': return this.visitPay(node);
153
+ case 'StorageDecl': return this.visitStorage(node);
154
+ case 'PdfDecl': return this.visitPdf(node);
155
+ case 'I18nDecl': return this.visitI18n(node);
156
+ case 'PushDecl': return this.visitPush(node);
157
+ case 'SearchDecl': return this.visitSearch(node);
158
+ case 'ImageDecl': return this.visitImage(node);
159
+ case 'CsvDecl': return this.visitCsv(node);
160
+ case 'LoggingDecl': return this.visitLogging(node);
161
+ case 'MigrateDecl': return this.visitMigrate(node);
162
+ case 'GrpcDecl': return this.visitGrpc(node);
163
+ case 'WebrtcDecl': return this.visitWebrtc(node);
164
+ case 'BlockchainDecl': return this.visitBlockchain(node);
165
+ default:
166
+ this.emit(`// unknown: ${node.type}`);
167
+ }
168
+ }
169
+
170
+ // ===== Type mapping =====
171
+
172
+ mapType(naideType) {
173
+ const map = {
174
+ 'str': 'String',
175
+ 'string': 'String',
176
+ 'int': 'int',
177
+ 'integer': 'int',
178
+ 'num': 'double',
179
+ 'number': 'double',
180
+ 'bool': 'bool',
181
+ 'boolean': 'bool',
182
+ 'list': 'List<dynamic>',
183
+ 'map': 'Map<String, dynamic>',
184
+ 'any': 'dynamic',
185
+ 'json': 'Map<String, dynamic>',
186
+ 'void': 'void',
187
+ 'auto': 'int',
188
+ 'timestamp': 'String',
189
+ };
190
+ return map[naideType] || 'dynamic';
191
+ }
192
+
193
+ // ===== Imports =====
194
+
195
+ visitUse(node) {
196
+ const raw = node.source ? this.rawString(node.source) : node.name;
197
+ const alias = node.alias || node.name;
198
+ const dartPkg = this.mapModuleName(raw);
199
+ this.addImport(`import '${dartPkg}';`);
200
+ }
201
+
202
+ visitUseDestructured(node) {
203
+ const raw = this.rawString(node.source);
204
+ const dartPkg = this.mapModuleName(raw);
205
+ this.addImport(`import '${dartPkg}';`);
206
+ }
207
+
208
+ mapModuleName(name) {
209
+ const clean = name.replace(/^['"]|['"]$/g, '');
210
+ const map = {
211
+ 'express': 'package:shelf/shelf.dart',
212
+ 'axios': 'package:http/http.dart',
213
+ 'lodash': 'dart:collection',
214
+ 'moment': 'package:intl/intl.dart',
215
+ 'fs': 'dart:io',
216
+ 'path': 'package:path/path.dart',
217
+ 'crypto': 'dart:convert',
218
+ 'uuid': 'package:uuid/uuid.dart',
219
+ };
220
+ return map[clean] || `package:${clean}/${clean}.dart`;
221
+ }
222
+
223
+ // ===== Functions =====
224
+
225
+ visitFunction(node) {
226
+ const async = node.isAsync ? ' async' : '';
227
+ const returnType = node.isAsync ? 'Future<dynamic>' : 'dynamic';
228
+ const params = node.params.map(p => {
229
+ let s = '';
230
+ if (p.spread) s += 'List<dynamic> ';
231
+ s += p.name;
232
+ if (p.defaultValue) s += ` = ${this.expr(p.defaultValue)}`;
233
+ return s;
234
+ }).join(', ');
235
+
236
+ const wasInFunction = this.inFunction;
237
+ this.inFunction = true;
238
+
239
+ this.emitTop(`${returnType} ${node.name}(${params})${async} {`);
240
+ const savedOutput = this.mainBody;
241
+ this.mainBody = [];
242
+ this.indent++;
243
+ for (const stmt of node.body) {
244
+ this.visitStatement(stmt);
245
+ }
246
+ this.indent--;
247
+ const bodyLines = this.mainBody;
248
+ this.mainBody = savedOutput;
249
+ this.inFunction = wasInFunction;
250
+
251
+ for (const line of bodyLines) {
252
+ this.emitTop(line);
253
+ }
254
+ this.emitTop('}');
255
+ this.emitTopRaw('');
256
+ }
257
+
258
+ visitReturn(node) {
259
+ if (node.value === null) {
260
+ this.emit('return;');
261
+ } else {
262
+ this.emit(`return ${this.expr(node.value)};`);
263
+ }
264
+ }
265
+
266
+ visitReturnStatus(node) {
267
+ this.emit(`// return status ${this.expr(node.statusCode)} with body ${this.expr(node.body)}`);
268
+ this.emit(`return Response.ok(jsonEncode(${this.expr(node.body)}), headers: {'content-type': 'application/json'});`);
269
+ }
270
+
271
+ visitReturnMethod(node) {
272
+ const val = this.expr(node.value);
273
+ switch (node.method) {
274
+ case 'redirect':
275
+ this.emit(`return Response.found(${val});`);
276
+ break;
277
+ case 'html':
278
+ this.emit(`return Response.ok(${val}, headers: {'content-type': 'text/html'});`);
279
+ break;
280
+ case 'text':
281
+ this.emit(`return Response.ok(${val}, headers: {'content-type': 'text/plain'});`);
282
+ break;
283
+ case 'file':
284
+ this.emit(`return Response.ok(File(${val}).readAsBytesSync());`);
285
+ break;
286
+ default:
287
+ this.emit(`return Response.ok(${val});`);
288
+ }
289
+ }
290
+
291
+ // ===== Variables =====
292
+
293
+ visitTypedVar(node) {
294
+ const keyword = node.isMut ? 'var' : 'final';
295
+ this.emit(`${keyword} ${node.name} = ${this.expr(node.value)};`);
296
+ }
297
+
298
+ visitAssignment(node) {
299
+ const value = this.expr(node.value);
300
+
301
+ if (node.target.type === 'Array') {
302
+ const names = node.target.elements.map(e => this.expr(e)).join(', ');
303
+ this.emit(`final __list = ${value};`);
304
+ node.target.elements.forEach((e, i) => {
305
+ this.emit(`var ${this.expr(e)} = __list[${i}];`);
306
+ });
307
+ } else if (node.target.type === 'Object') {
308
+ const tempVar = '__map';
309
+ this.emit(`final ${tempVar} = ${value};`);
310
+ for (const p of node.target.properties) {
311
+ const key = p.type === 'shorthand' ? p.key : p.key;
312
+ const varName = p.type === 'shorthand' ? p.key : this.expr(p.value);
313
+ this.emit(`var ${varName} = ${tempVar}['${key}'];`);
314
+ }
315
+ } else {
316
+ this.emit(`${this.expr(node.target)} = ${value};`);
317
+ }
318
+ }
319
+
320
+ visitCompoundAssign(node) {
321
+ this.emit(`${this.expr(node.target)} ${node.op} ${this.expr(node.value)};`);
322
+ }
323
+
324
+ // ===== Control flow =====
325
+
326
+ visitIf(node) {
327
+ this.emit(`if (${this.expr(node.condition)}) {`);
328
+ this.indent++;
329
+ for (const stmt of node.body) this.visitStatement(stmt);
330
+ this.indent--;
331
+
332
+ for (const elif of node.elifs) {
333
+ this.emit(`} else if (${this.expr(elif.condition)}) {`);
334
+ this.indent++;
335
+ for (const stmt of elif.body) this.visitStatement(stmt);
336
+ this.indent--;
337
+ }
338
+
339
+ if (node.elseBody) {
340
+ this.emit('} else {');
341
+ this.indent++;
342
+ for (const stmt of node.elseBody) this.visitStatement(stmt);
343
+ this.indent--;
344
+ }
345
+ this.emit('}');
346
+ }
347
+
348
+ visitEach(node) {
349
+ const collection = this.expr(node.collection);
350
+ if (node.key) {
351
+ this.emit(`for (var __entry in ${collection}.entries) {`);
352
+ this.indent++;
353
+ this.emit(`var ${node.key} = __entry.key;`);
354
+ this.emit(`var ${node.value} = __entry.value;`);
355
+ for (const stmt of node.body) this.visitStatement(stmt);
356
+ this.indent--;
357
+ this.emit('}');
358
+ } else {
359
+ this.emit(`for (var ${node.value} in ${collection}) {`);
360
+ this.indent++;
361
+ for (const stmt of node.body) this.visitStatement(stmt);
362
+ this.indent--;
363
+ this.emit('}');
364
+ }
365
+ }
366
+
367
+ visitFor(node) {
368
+ const varName = node.varName;
369
+ const start = this.expr(node.start);
370
+ const end = this.expr(node.end);
371
+ this.emit(`for (var ${varName} = ${start}; ${varName} < ${end}; ${varName}++) {`);
372
+ this.indent++;
373
+ for (const stmt of node.body) this.visitStatement(stmt);
374
+ this.indent--;
375
+ this.emit('}');
376
+ }
377
+
378
+ visitWhile(node) {
379
+ this.emit(`while (${this.expr(node.condition)}) {`);
380
+ this.indent++;
381
+ for (const stmt of node.body) this.visitStatement(stmt);
382
+ this.indent--;
383
+ this.emit('}');
384
+ }
385
+
386
+ visitMatch(node) {
387
+ this.emit(`switch (${this.expr(node.value)}) {`);
388
+ this.indent++;
389
+ for (const c of node.cases) {
390
+ if (c.pattern.type === 'DefaultPattern') {
391
+ this.emit('default:');
392
+ } else {
393
+ this.emit(`case ${this.expr(c.pattern)}:`);
394
+ }
395
+ this.indent++;
396
+ for (const stmt of c.body) this.visitStatement(stmt);
397
+ this.emit('break;');
398
+ this.indent--;
399
+ }
400
+ this.indent--;
401
+ this.emit('}');
402
+ }
403
+
404
+ visitTry(node) {
405
+ this.emit('try {');
406
+ this.indent++;
407
+ for (const stmt of node.body) this.visitStatement(stmt);
408
+ this.indent--;
409
+
410
+ if (node.catchBody) {
411
+ const catchParam = node.catchVar || 'e';
412
+ this.emit(`} catch (${catchParam}) {`);
413
+ this.indent++;
414
+ for (const stmt of node.catchBody) this.visitStatement(stmt);
415
+ this.indent--;
416
+ }
417
+
418
+ if (node.ensureBody) {
419
+ this.emit('} finally {');
420
+ this.indent++;
421
+ for (const stmt of node.ensureBody) this.visitStatement(stmt);
422
+ this.indent--;
423
+ }
424
+ this.emit('}');
425
+ }
426
+
427
+ // ===== Server (dart:io HttpServer / shelf) =====
428
+
429
+ visitServer(node) {
430
+ this.usesServer = true;
431
+ this.addImport("import 'dart:io';");
432
+ this.addImport("import 'dart:convert';");
433
+
434
+ this.emit(`final server = await HttpServer.bind('0.0.0.0', ${node.port ? this.expr(node.port) : '3000'});`);
435
+ this.emit(`print('Server running on port \${server.port}');`);
436
+ this.emitRaw('');
437
+ this.emit(`await for (HttpRequest request in server) {`);
438
+ this.indent++;
439
+ this.emit(`final path = request.uri.path;`);
440
+ this.emit(`final method = request.method;`);
441
+ this.emitRaw('');
442
+
443
+ const routes = [];
444
+ const errorHandlers = [];
445
+
446
+ for (const child of node.routes) {
447
+ if (child.type === 'Route') {
448
+ routes.push(child);
449
+ } else if (child.type === 'ErrorHandler') {
450
+ errorHandlers.push(child);
451
+ } else if (child.type === 'StaticDecl') {
452
+ this.emit(`// static files: ${this.rawString(child.path)}`);
453
+ } else if (child.type === 'CorsDecl') {
454
+ this.emit(`request.response.headers.add('Access-Control-Allow-Origin', ${this.expr(child.origins)});`);
455
+ this.emit(`request.response.headers.add('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');`);
456
+ this.emit(`request.response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization');`);
457
+ } else if (child.type === 'AuthDecl') {
458
+ this.visitAuth('app', child);
459
+ } else if (child.type === 'SchemaDecl') {
460
+ this.visitSchema(child);
461
+ } else if (child.type === 'CrudDecl') {
462
+ this.emit(`// CRUD: ${this.rawString(child.path)} for ${child.schemaName}`);
463
+ } else {
464
+ this.visitStatement(child);
465
+ }
466
+ }
467
+
468
+ let first = true;
469
+ for (const route of routes) {
470
+ const method = route.method === 'del' ? 'DELETE' : route.method.toUpperCase();
471
+ const path = this.rawString(route.path);
472
+ const keyword = first ? 'if' : '} else if';
473
+ first = false;
474
+ this.emit(`${keyword} (method == '${method}' && path == '${path}') {`);
475
+ this.indent++;
476
+ this.emit('try {');
477
+ this.indent++;
478
+ for (const stmt of route.body) {
479
+ if (stmt.type === 'Return' && stmt.value !== null) {
480
+ this.emit(`request.response.headers.contentType = ContentType.json;`);
481
+ this.emit(`request.response.write(jsonEncode(${this.expr(stmt.value)}));`);
482
+ } else {
483
+ this.visitStatement(stmt);
484
+ }
485
+ }
486
+ this.indent--;
487
+ this.emit('} catch (e) {');
488
+ this.indent++;
489
+ this.emit(`request.response.statusCode = 500;`);
490
+ this.emit(`request.response.write(jsonEncode({'error': e.toString()}));`);
491
+ this.indent--;
492
+ this.emit('}');
493
+ this.indent--;
494
+ }
495
+
496
+ if (routes.length > 0) {
497
+ this.emit('} else {');
498
+ this.indent++;
499
+ this.emit(`request.response.statusCode = 404;`);
500
+ this.emit(`request.response.write(jsonEncode({'error': 'Not found'}));`);
501
+ this.indent--;
502
+ this.emit('}');
503
+ }
504
+
505
+ this.emit('await request.response.close();');
506
+ this.indent--;
507
+ this.emit('}');
508
+ }
509
+
510
+ // ===== Bot =====
511
+
512
+ visitBot(node) {
513
+ const tokenExpr = node.token ? this.expr(node.token) : "Platform.environment['BOT_TOKEN'] ?? ''";
514
+ this.addImport("import 'dart:io';");
515
+ this.emit(`// Bot: ${node.name} — use a Dart bot library (e.g., nyxx for Discord)`);
516
+ this.emit(`// token: ${tokenExpr}`);
517
+ for (const handler of node.handlers) {
518
+ if (handler.type === 'BotEvent') {
519
+ const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
520
+ this.emit(`// on ${evtName}`);
521
+ for (const stmt of handler.body) this.visitStatement(stmt);
522
+ } else if (handler.type === 'BotSlashCmd') {
523
+ const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
524
+ this.emit(`// slash command: ${cmdName}`);
525
+ for (const stmt of handler.body) this.visitStatement(stmt);
526
+ }
527
+ }
528
+ }
529
+
530
+ // ===== Model (class) =====
531
+
532
+ visitModel(node) {
533
+ this.models.set(node.name, node);
534
+
535
+ const ext = node.parent ? ` extends ${node.parent}` : '';
536
+ this.emitTop(`class ${node.name}${ext} {`);
537
+
538
+ const parentModel = node.parent ? this.models.get(node.parent) : null;
539
+ const parentFields = parentModel ? parentModel.fields : [];
540
+ const allFields = [...parentFields, ...node.fields];
541
+
542
+ // Fields
543
+ for (const f of node.fields) {
544
+ const dartType = f.fieldType ? this.mapType(f.fieldType) : 'dynamic';
545
+ this.emitTop(` ${dartType} ${f.name};`);
546
+ }
547
+
548
+ if (node.fields.length > 0) {
549
+ this.emitTopRaw('');
550
+ }
551
+
552
+ // Constructor
553
+ if (allFields.length > 0) {
554
+ const constructorParams = node.fields.map(f => {
555
+ if (f.defaultValue) return `this.${f.name} = ${this.expr(f.defaultValue)}`;
556
+ return `required this.${f.name}`;
557
+ }).join(', ');
558
+
559
+ if (node.parent) {
560
+ const superArgs = parentFields.map(f => `${f.name}: ${f.name}`).join(', ');
561
+ const parentParams = parentFields.map(f => `required super.${f.name}`).join(', ');
562
+ const allParams = [parentParams, constructorParams].filter(s => s).join(', ');
563
+ this.emitTop(` ${node.name}({${allParams}});`);
564
+ } else {
565
+ this.emitTop(` ${node.name}({${constructorParams}});`);
566
+ }
567
+ this.emitTopRaw('');
568
+ }
569
+
570
+ // Methods
571
+ for (const method of node.methods) {
572
+ const async = method.isAsync ? ' async' : '';
573
+ const returnType = method.isAsync ? 'Future<dynamic>' : 'dynamic';
574
+ const params = method.params.map(p => {
575
+ let s = '';
576
+ if (p.spread) s += 'List<dynamic> ';
577
+ s += p.name;
578
+ if (p.defaultValue) s += ` = ${this.expr(p.defaultValue)}`;
579
+ return s;
580
+ }).join(', ');
581
+
582
+ const savedOutput = this.mainBody;
583
+ this.mainBody = [];
584
+ const wasInClass = this.inClass;
585
+ this.inClass = true;
586
+ this.indent = 2;
587
+ for (const stmt of method.body) {
588
+ this.visitStatement(stmt);
589
+ }
590
+ const bodyLines = this.mainBody;
591
+ this.mainBody = savedOutput;
592
+ this.inClass = wasInClass;
593
+ this.indent = 0;
594
+
595
+ this.emitTop(` ${returnType} ${method.name}(${params})${async} {`);
596
+ for (const line of bodyLines) {
597
+ this.emitTop(line);
598
+ }
599
+ this.emitTop(' }');
600
+ this.emitTopRaw('');
601
+ }
602
+
603
+ this.emitTop('}');
604
+ this.emitTopRaw('');
605
+ }
606
+
607
+ // ===== Events =====
608
+
609
+ visitOn(node) {
610
+ const event = this.expr(node.event);
611
+ this.emit(`// on ${event}`);
612
+ for (const stmt of node.body) this.visitStatement(stmt);
613
+ }
614
+
615
+ // ===== Logging and errors =====
616
+
617
+ visitLog(node) {
618
+ const args = node.args.map(a => this.expr(a)).join(', ');
619
+ this.emit(`print(${args});`);
620
+ }
621
+
622
+ visitThrow(node) {
623
+ const value = this.expr(node.value);
624
+ if (node.value.type === 'String') {
625
+ this.emit(`throw Exception(${value});`);
626
+ } else {
627
+ this.emit(`throw ${value};`);
628
+ }
629
+ }
630
+
631
+ // ===== Database =====
632
+
633
+ visitDbConnect(node) {
634
+ this.emit(`// db connect: ${this.expr(node.connectionString)}`);
635
+ }
636
+
637
+ visitDbDir(node) {
638
+ const raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'data/';
639
+ this.emit(`// db dir: ${raw}`);
640
+ }
641
+
642
+ visitDbSql(node) {
643
+ const driverRaw = node.driver.raw || node.driver.parts?.map(p => p.value).join('') || 'sqlite';
644
+ this.emit(`// db sql driver: ${driverRaw}`);
645
+ if (driverRaw === 'sqlite') {
646
+ this.addImport("import 'package:sqflite/sqflite.dart';");
647
+ const conn = node.connection ? this.stringValue(node.connection) : "'data.db'";
648
+ this.emit(`final __db = await openDatabase(${conn});`);
649
+ } else {
650
+ const conn = node.connection ? this.stringValue(node.connection) : "'localhost'";
651
+ this.emit(`// connect to ${driverRaw}: ${conn}`);
652
+ }
653
+ this.emitRaw('');
654
+ }
655
+
656
+ visitAwaitAllStatement(node) {
657
+ const exprs = node.expressions.map(e => this.expr(e)).join(', ');
658
+ this.emit(`await Future.wait([${exprs}]);`);
659
+ }
660
+
661
+ // ===== Schema =====
662
+
663
+ visitSchema(node) {
664
+ this.schemas.set(node.name, node);
665
+ this.emitTop(`class ${node.name} {`);
666
+ for (const field of node.fields) {
667
+ const dartType = this.mapType(field.type);
668
+ const nullable = field.modifiers.some(m => m.name === 'optional') ? '?' : '';
669
+ this.emitTop(` ${dartType}${nullable} ${field.name};`);
670
+ }
671
+ this.emitTopRaw('');
672
+ const constructorParams = node.fields.map(f => {
673
+ const isRequired = !f.modifiers.some(m => m.name === 'optional') && f.type !== 'auto' && f.type !== 'timestamp';
674
+ if (isRequired) return `required this.${f.name}`;
675
+ return `this.${f.name}`;
676
+ }).join(', ');
677
+ this.emitTop(` ${node.name}({${constructorParams}});`);
678
+ this.emitTopRaw('');
679
+ this.emitTop(` Map<String, dynamic> toJson() => {`);
680
+ for (const field of node.fields) {
681
+ this.emitTop(` '${field.name}': ${field.name},`);
682
+ }
683
+ this.emitTop(` };`);
684
+ this.emitTop(`}`);
685
+ this.emitTopRaw('');
686
+ }
687
+
688
+ // ===== Auth =====
689
+
690
+ visitAuth(appName, node) {
691
+ const secret = this.expr(node.secret);
692
+ this.authSecret = secret;
693
+ this.emit(`// auth: JWT secret = ${secret}`);
694
+ }
695
+
696
+ // ===== CORS =====
697
+
698
+ visitCors(appName, node) {
699
+ const origins = this.expr(node.origins);
700
+ this.emit(`// CORS: allow origins ${origins}`);
701
+ }
702
+
703
+ // ===== Rate limit =====
704
+
705
+ visitLimit(appName, node) {
706
+ const path = this.rawString(node.path);
707
+ const max = this.expr(node.max);
708
+ const window = this.expr(node.window);
709
+ this.emit(`// rate limit: ${max} per ${window} on ${path}`);
710
+ }
711
+
712
+ // ===== Env =====
713
+
714
+ visitEnv(node) {
715
+ this.addImport("import 'dart:io';");
716
+ for (const v of node.vars) {
717
+ let defaultVal = null;
718
+ for (const mod of v.modifiers) {
719
+ if (mod.name === 'default') {
720
+ defaultVal = this.expr(mod.args[0]);
721
+ }
722
+ }
723
+ if (defaultVal !== null) {
724
+ this.emit(`final ${v.name} = Platform.environment['${v.name}'] ?? ${defaultVal};`);
725
+ } else {
726
+ this.emit(`final ${v.name} = Platform.environment['${v.name}'];`);
727
+ }
728
+ }
729
+ this.emitRaw('');
730
+ }
731
+
732
+ // ===== Every (scheduler) =====
733
+
734
+ visitEvery(node) {
735
+ const interval = this.expr(node.interval);
736
+ this.emit(`// scheduled task every ${interval}`);
737
+ this.emit(`Timer.periodic(Duration(seconds: 60), (_) {`);
738
+ this.indent++;
739
+ for (const stmt of node.body) this.visitStatement(stmt);
740
+ this.indent--;
741
+ this.emit('});');
742
+ this.emitRaw('');
743
+ }
744
+
745
+ // ===== Watch =====
746
+
747
+ visitWatch(node) {
748
+ const params = node.params.length > 0 ? node.params.join(', ') : 'event';
749
+ this.emit(`// watch: ${node.eventName}`);
750
+ this.emit(`// handler(${params})`);
751
+ for (const stmt of node.body) this.visitStatement(stmt);
752
+ }
753
+
754
+ // ===== Static =====
755
+
756
+ visitStatic(appName, node) {
757
+ let raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'public';
758
+ this.emit(`// static files from: ${raw}`);
759
+ }
760
+
761
+ // ===== WebSocket =====
762
+
763
+ visitWs(node) {
764
+ this.addImport("import 'dart:io';");
765
+ this.addImport("import 'dart:convert';");
766
+ const path = this.rawString(node.path);
767
+ this.emit(`// WebSocket endpoint: ${path}`);
768
+ for (const evt of (node.events || [])) {
769
+ const evtName = evt.name.raw || evt.name.parts?.map(p => p.value).join('');
770
+ this.emit(`// ws event: ${evtName}`);
771
+ for (const stmt of evt.body) this.visitStatement(stmt);
772
+ }
773
+ }
774
+
775
+ // ===== Group =====
776
+
777
+ visitGroup(appName, node) {
778
+ const prefix = this.rawString(node.prefix);
779
+ this.emit(`// route group: ${prefix}`);
780
+ for (const child of node.routes) {
781
+ this.visitStatement(child);
782
+ }
783
+ }
784
+
785
+ // ===== Error handler =====
786
+
787
+ visitErrorHandler(appName, node) {
788
+ this.emit(`// error handler`);
789
+ for (const stmt of node.body) this.visitStatement(stmt);
790
+ }
791
+
792
+ // ===== Cookie =====
793
+
794
+ visitCookie(appName, node) {
795
+ this.emit(`// cookies enabled`);
796
+ }
797
+
798
+ // ===== Upload =====
799
+
800
+ visitUpload(appName, node) {
801
+ const path = this.rawString(node.path);
802
+ this.emit(`// upload endpoint: ${path}`);
803
+ for (const stmt of node.body) this.visitStatement(stmt);
804
+ }
805
+
806
+ // ===== Session =====
807
+
808
+ visitSession(appName, node) {
809
+ this.emit(`// session enabled`);
810
+ }
811
+
812
+ // ===== View =====
813
+
814
+ visitView(appName, node) {
815
+ const dir = this.rawString(node.dir);
816
+ this.emit(`// view templates from: ${dir}`);
817
+ }
818
+
819
+ // ===== SSE =====
820
+
821
+ visitSse(appName, node) {
822
+ const path = this.rawString(node.path);
823
+ this.emit(`// SSE endpoint: ${path}`);
824
+ }
825
+
826
+ // ===== Cache =====
827
+
828
+ visitCache(appName, node) {
829
+ const path = this.rawString(node.path);
830
+ this.emit(`// cache: ${path}`);
831
+ }
832
+
833
+ // ===== MiddlewareRef =====
834
+
835
+ visitMiddlewareRef(appName, node) {
836
+ this.emit(`// middleware: ${node.name}`);
837
+ }
838
+
839
+ // ===== ReturnRender =====
840
+
841
+ visitReturnRender(node) {
842
+ const template = this.expr(node.template);
843
+ this.emit(`// render template: ${template}`);
844
+ }
845
+
846
+ // ===== Validate =====
847
+
848
+ visitValidate(appName, node) {
849
+ this.emit(`// validate: ${node.schemaName}`);
850
+ }
851
+
852
+ // ===== Tests =====
853
+
854
+ visitTest(node) {
855
+ this.hasTests = true;
856
+ this.addImport("import 'package:test/test.dart';");
857
+ const name = this.generateString(node.name);
858
+ const needsAsync = this.bodyUsesAwait(node.body);
859
+ const asyncPrefix = needsAsync ? ' async' : '';
860
+ this.emit(`test(${name}, ()${asyncPrefix} {`);
861
+ this.indent++;
862
+ for (const stmt of node.body) this.visitStatement(stmt);
863
+ this.indent--;
864
+ this.emit('});');
865
+ this.emitRaw('');
866
+ }
867
+
868
+ visitAssert(node) {
869
+ this.hasAsserts = true;
870
+ this.addImport("import 'package:test/test.dart';");
871
+ const exprNode = node.expr;
872
+ if (exprNode.type === 'Binary' && (exprNode.op === '===' || exprNode.op === '==')) {
873
+ this.emit(`expect(${this.expr(exprNode.left)}, equals(${this.expr(exprNode.right)}));`);
874
+ } else if (exprNode.type === 'Binary' && (exprNode.op === '!==' || exprNode.op === '!=')) {
875
+ this.emit(`expect(${this.expr(exprNode.left)}, isNot(equals(${this.expr(exprNode.right)})));`);
876
+ } else {
877
+ this.emit(`expect(${this.expr(exprNode)}, isTrue);`);
878
+ }
879
+ }
880
+
881
+ // ===== Queue =====
882
+
883
+ visitQueue(node) {
884
+ this.emit(`// queue: ${node.name}`);
885
+ for (const job of node.jobs) {
886
+ const name = job.name.raw || job.name.parts?.map(p => p.value).join('');
887
+ this.emit(`// job: ${name}`);
888
+ for (const stmt of job.body) this.visitStatement(stmt);
889
+ }
890
+ }
891
+
892
+ // ===== OpenAPI =====
893
+
894
+ visitOpenapi(appName, node) {
895
+ this.emit(`// OpenAPI spec endpoint`);
896
+ }
897
+
898
+ // ===== ReturnRedirect =====
899
+
900
+ visitReturnRedirect(node) {
901
+ this.emit(`// redirect ${this.expr(node.statusCode)} -> ${this.expr(node.url)}`);
902
+ }
903
+
904
+ // ===== ReturnDownload =====
905
+
906
+ visitReturnDownload(node) {
907
+ this.emit(`// download: ${this.expr(node.filePath)}`);
908
+ }
909
+
910
+ // ===== Prompt =====
911
+
912
+ visitPrompt(node) {
913
+ this.emit(`// prompt template: ${node.name}`);
914
+ this.emit(`String ${node.name}(Map<String, String> vars) {`);
915
+ this.indent++;
916
+ const lines = node.lines.map(l => this.generateString(l));
917
+ const template = lines.length === 1 ? lines[0] : lines.join(' + \'\\n\' + ');
918
+ this.emit(`var template = ${template};`);
919
+ this.emit(`vars.forEach((k, v) { template = template.replaceAll('{' + k + '}', v); });`);
920
+ this.emit(`return template;`);
921
+ this.indent--;
922
+ this.emit('}');
923
+ this.emitRaw('');
924
+ }
925
+
926
+ // ===== Page =====
927
+
928
+ visitPage(node) {
929
+ this.addImport("import 'dart:io';");
930
+ const filename = this.rawString(node.filename);
931
+ const headParts = [];
932
+ const bodyParts = [];
933
+ let pageTitle = 'NAIDE Page';
934
+
935
+ for (const el of node.elements) {
936
+ this.classifyPageElement(el, headParts, bodyParts, (t) => { pageTitle = t; });
937
+ }
938
+
939
+ this.emit(`File('${filename}').writeAsStringSync('''<!DOCTYPE html>`);
940
+ this.emit(`<html lang="en"><head><meta charset="UTF-8">`);
941
+ this.emit(`<meta name="viewport" content="width=device-width, initial-scale=1.0">`);
942
+ this.emit(`<title>${pageTitle}</title>`);
943
+ for (const h of headParts) this.emit(h);
944
+ this.emit(`</head><body>`);
945
+ for (const b of bodyParts) this.emit(b);
946
+ this.emit(`</body></html>''');`);
947
+ this.emitRaw('');
948
+ }
949
+
950
+ classifyPageElement(el, head, body, setTitle) {
951
+ const tag = el.tag;
952
+ const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
953
+ const arg1 = el.args[1] ? this.rawString(el.args[1]) : '';
954
+ if (tag === 'title') { setTitle(arg0); return; }
955
+ if (tag === 'style' || tag === 'link') { head.push(`<link rel="stylesheet" href="${arg0}">`); return; }
956
+ if (tag === 'meta') { head.push(`<meta name="${arg0}" content="${arg1}">`); return; }
957
+ if (tag === 'script') { body.push(`<script src="${arg0}"></script>`); return; }
958
+ if (tag === 'img') { body.push(`<img src="${arg0}" alt="${arg1}">`); return; }
959
+ if (tag === 'a') { body.push(`<a href="${arg0}">${arg1}</a>`); return; }
960
+ if (tag === 'input') { body.push(`<input type="${arg0}" name="${arg1}">`); return; }
961
+ if (el.children && el.children.length > 0) {
962
+ const cls = arg0 ? ` class="${arg0}"` : '';
963
+ body.push(`<${tag}${cls}>`);
964
+ for (const child of el.children) this.classifyPageElement(child, head, body, setTitle);
965
+ body.push(`</${tag}>`);
966
+ } else {
967
+ body.push(`<${tag}>${arg0}</${tag}>`);
968
+ }
969
+ }
970
+
971
+ // ===== CLI App =====
972
+
973
+ visitCli(node) {
974
+ const desc = node.description ? this.rawString(node.description) : node.name;
975
+ this.addImport("import 'dart:io';");
976
+ this.emit(`// CLI: ${desc}`);
977
+ this.emit(`final args = <String, dynamic>{};`);
978
+ this.emit(`final __argv = Platform.executableArguments;`);
979
+
980
+ for (const arg of node.args) {
981
+ const argName = this.rawString(arg.name);
982
+ this.emit(`// arg: --${argName} (${arg.type})`);
983
+ }
984
+ for (const flag of node.flags) {
985
+ const l = this.rawString(flag.long);
986
+ this.emit(`// flag: --${l}`);
987
+ }
988
+ this.emitRaw('');
989
+
990
+ if (node.run) {
991
+ for (const stmt of node.run.body) this.visitStatement(stmt);
992
+ }
993
+ this.emitRaw('');
994
+ }
995
+
996
+ // ===== Mail =====
997
+
998
+ visitMail(node) {
999
+ this.addImport("import 'package:mailer/mailer.dart';");
1000
+ this.addImport("import 'package:mailer/smtp_server.dart';");
1001
+ const host = this.generateString(node.host);
1002
+ const port = this.expr(node.port);
1003
+ const user = node.user ? this.expr(node.user) : "Platform.environment['MAIL_USER'] ?? ''";
1004
+ const pass = node.pass ? this.expr(node.pass) : "Platform.environment['MAIL_PASS'] ?? ''";
1005
+ this.emit(`final smtpServer = SmtpServer(${host}, port: ${port}, username: ${user}, password: ${pass});`);
1006
+ this.emitRaw('');
1007
+ }
1008
+
1009
+ // ===== Desktop =====
1010
+
1011
+ visitDesktop(node) {
1012
+ this.emit(`// Desktop app: ${node.name} — use flutter desktop or dart:ffi`);
1013
+ const title = node.title ? this.rawString(node.title) : node.name;
1014
+ const width = node.width ? this.expr(node.width) : '800';
1015
+ const height = node.height ? this.expr(node.height) : '600';
1016
+ this.emit(`// title: ${title}, size: ${width}x${height}`);
1017
+ this.emitRaw('');
1018
+ }
1019
+
1020
+ // ===== Screen =====
1021
+
1022
+ visitScreen(node) {
1023
+ this.emit(`// Screen: ${node.name} — use Flutter widgets`);
1024
+ for (const el of node.elements) {
1025
+ const tag = el.tag;
1026
+ const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
1027
+ this.emit(`// widget: ${tag}('${arg0}')`);
1028
+ }
1029
+ this.emitRaw('');
1030
+ }
1031
+
1032
+ // ===== OAuth =====
1033
+
1034
+ visitOauth(node) {
1035
+ const provider = this.rawString(node.provider);
1036
+ const clientId = this.expr(node.clientId);
1037
+ this.emit(`// OAuth: ${provider}, clientId: ${clientId}`);
1038
+ this.emitRaw('');
1039
+ }
1040
+
1041
+ // ===== Pay =====
1042
+
1043
+ visitPay(node) {
1044
+ const provider = this.rawString(node.provider);
1045
+ this.emit(`// Payment: ${provider}`);
1046
+ this.emitRaw('');
1047
+ }
1048
+
1049
+ // ===== Storage =====
1050
+
1051
+ visitStorage(node) {
1052
+ const provider = this.rawString(node.provider);
1053
+ const bucket = this.expr(node.bucket);
1054
+ this.emit(`// Storage: ${provider}, bucket: ${bucket}`);
1055
+ this.emitRaw('');
1056
+ }
1057
+
1058
+ // ===== PDF =====
1059
+
1060
+ visitPdf(node) {
1061
+ const filename = this.rawString(node.filename);
1062
+ this.emit(`// PDF generation: ${filename} — use pdf package`);
1063
+ this.addImport("import 'package:pdf/pdf.dart';");
1064
+ this.addImport("import 'package:pdf/widgets.dart' as pw;");
1065
+ this.addImport("import 'dart:io';");
1066
+ this.emit(`final __pdf = pw.Document();`);
1067
+ this.emit(`__pdf.addPage(pw.Page(build: (pw.Context context) {`);
1068
+ this.indent++;
1069
+ this.emit(`return pw.Column(children: [`);
1070
+ this.indent++;
1071
+ for (const el of node.elements) {
1072
+ const tag = el.tag;
1073
+ const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
1074
+ if (tag === 'title' || tag === 'h1') {
1075
+ this.emit(`pw.Text('${arg0}', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)),`);
1076
+ } else if (tag === 'h2' || tag === 'heading') {
1077
+ this.emit(`pw.Text('${arg0}', style: pw.TextStyle(fontSize: 18, fontWeight: pw.FontWeight.bold)),`);
1078
+ } else {
1079
+ this.emit(`pw.Text('${arg0}'),`);
1080
+ }
1081
+ }
1082
+ this.indent--;
1083
+ this.emit(`]);`);
1084
+ this.indent--;
1085
+ this.emit(`}));`);
1086
+ this.emit(`File('${filename}').writeAsBytesSync(await __pdf.save());`);
1087
+ this.emit(`print('Generated: ${filename}');`);
1088
+ this.emitRaw('');
1089
+ }
1090
+
1091
+ // ===== i18n =====
1092
+
1093
+ visitI18n(node) {
1094
+ const defaultLang = node.defaultLang ? this.rawString(node.defaultLang) : 'en';
1095
+ this.emit(`// i18n: default language = ${defaultLang}`);
1096
+ this.addImport("import 'dart:convert';");
1097
+ this.addImport("import 'dart:io';");
1098
+ this.emit(`var __i18nLang = '${defaultLang}';`);
1099
+ this.emit(`final __i18nData = <String, Map<String, dynamic>>{};`);
1100
+ for (const lang of node.langs) {
1101
+ const code = this.rawString(lang.code);
1102
+ const file = this.rawString(lang.file);
1103
+ const dir = this.rawString(node.dir);
1104
+ this.emit(`__i18nData['${code}'] = jsonDecode(File('${dir}/${file}').readAsStringSync());`);
1105
+ }
1106
+ this.emitRaw('');
1107
+ }
1108
+
1109
+ // ===== Push =====
1110
+
1111
+ visitPush(node) {
1112
+ this.emit(`// Push notifications — use firebase_messaging or web_push package`);
1113
+ this.emitRaw('');
1114
+ }
1115
+
1116
+ // ===== Search =====
1117
+
1118
+ visitSearch(node) {
1119
+ const engine = this.rawString(node.engine);
1120
+ this.emit(`// Search engine: ${engine}`);
1121
+ this.emitRaw('');
1122
+ }
1123
+
1124
+ // ===== Image =====
1125
+
1126
+ visitImage(node) {
1127
+ const input = this.expr(node.input);
1128
+ this.emit(`// Image processing: ${input} — use image package`);
1129
+ this.addImport("import 'package:image/image.dart' as img;");
1130
+ this.emit(`var __img = img.decodeImage(File(${input}).readAsBytesSync())!;`);
1131
+ for (const op of node.operations) {
1132
+ if (op.op === 'resize') {
1133
+ this.emit(`__img = img.copyResize(__img, width: ${op.args[0] || 800});`);
1134
+ } else if (op.op === 'rotate') {
1135
+ this.emit(`__img = img.copyRotate(__img, angle: ${op.args[0] || 90});`);
1136
+ } else if (op.op === 'grayscale' || op.op === 'greyscale') {
1137
+ this.emit(`__img = img.grayscale(__img);`);
1138
+ } else if (op.op === 'flip') {
1139
+ this.emit(`__img = img.flipVertical(__img);`);
1140
+ } else if (op.op === 'blur') {
1141
+ this.emit(`__img = img.gaussianBlur(__img, radius: ${op.args[0] || 5});`);
1142
+ } else if (op.op === 'crop') {
1143
+ this.emit(`__img = img.copyCrop(__img, x: ${op.args[0] || 0}, y: ${op.args[1] || 0}, width: ${op.args[2] || 100}, height: ${op.args[3] || 100});`);
1144
+ }
1145
+ }
1146
+ const output = node.output ? this.expr(node.output) : input;
1147
+ this.emit(`File(${output}).writeAsBytesSync(img.encodePng(__img));`);
1148
+ this.emitRaw('');
1149
+ }
1150
+
1151
+ // ===== CSV =====
1152
+
1153
+ visitCsv(node) {
1154
+ const name = this.rawString(node.name);
1155
+ const format = node.format ? this.rawString(node.format) : 'csv';
1156
+ this.emit(`// CSV/Excel export: ${name}.${format}`);
1157
+ this.emitRaw('');
1158
+ }
1159
+
1160
+ // ===== Logging =====
1161
+
1162
+ visitLogging(node) {
1163
+ const name = this.rawString(node.name);
1164
+ this.addImport("import 'package:logging/logging.dart';");
1165
+ this.emit(`final logger = Logger('${name}');`);
1166
+ this.emitRaw('');
1167
+ }
1168
+
1169
+ // ===== Migrate =====
1170
+
1171
+ visitMigrate(node) {
1172
+ const name = this.rawString(node.name);
1173
+ this.emit(`// migration: ${name}`);
1174
+ this.emitRaw('');
1175
+ }
1176
+
1177
+ // ===== gRPC =====
1178
+
1179
+ visitGrpc(node) {
1180
+ const name = this.rawString(node.name);
1181
+ this.emit(`// gRPC service: ${name}`);
1182
+ this.emitRaw('');
1183
+ }
1184
+
1185
+ // ===== WebRTC =====
1186
+
1187
+ visitWebrtc(node) {
1188
+ this.emit(`// WebRTC signaling server`);
1189
+ this.emitRaw('');
1190
+ }
1191
+
1192
+ // ===== Blockchain =====
1193
+
1194
+ visitBlockchain(node) {
1195
+ const name = this.rawString(node.name);
1196
+ this.emit(`// Blockchain: ${name}`);
1197
+ this.emitRaw('');
1198
+ }
1199
+
1200
+ // ===== Expression generation =====
1201
+
1202
+ expr(node) {
1203
+ if (!node) return 'null';
1204
+
1205
+ switch (node.type) {
1206
+ case 'Number': return node.value;
1207
+ case 'String': return this.generateString(node.value);
1208
+ case 'Bool': return node.value ? 'true' : 'false';
1209
+ case 'Null': return 'null';
1210
+ case 'Self': return 'this';
1211
+ case 'Identifier': return this.mapIdentifier(node.name);
1212
+
1213
+ case 'Binary': {
1214
+ const op = this.mapBinaryOp(node.op);
1215
+ const l = this.expr(node.left);
1216
+ const r = this.expr(node.right);
1217
+ const simple = node.left.type !== 'Binary' && node.right.type !== 'Binary';
1218
+ return simple ? `${l} ${op} ${r}` : `(${l} ${op} ${r})`;
1219
+ }
1220
+
1221
+ case 'Unary': {
1222
+ const op = this.mapUnaryOp(node.op);
1223
+ return `${op}${this.expr(node.expr)}`;
1224
+ }
1225
+
1226
+ case 'Await':
1227
+ return `await ${this.expr(node.expr)}`;
1228
+
1229
+ case 'AwaitAllExpr':
1230
+ return `await Future.wait(${this.expr(node.expr)})`;
1231
+
1232
+ case 'Spread':
1233
+ return `...${this.expr(node.expr)}`;
1234
+
1235
+ case 'New':
1236
+ return this.expr(node.expr);
1237
+
1238
+ case 'TypeOf':
1239
+ return `${this.expr(node.expr)}.runtimeType.toString()`;
1240
+
1241
+ case 'MemberAccess':
1242
+ return this.mapMemberAccess(node);
1243
+
1244
+ case 'OptionalAccess':
1245
+ return `${this.expr(node.object)}?.${node.property}`;
1246
+
1247
+ case 'Call':
1248
+ return this.generateCall(node);
1249
+
1250
+ case 'IndexAccess':
1251
+ return `${this.expr(node.object)}[${this.expr(node.index)}]`;
1252
+
1253
+ case 'Array':
1254
+ return `[${node.elements.map(e => this.expr(e)).join(', ')}]`;
1255
+
1256
+ case 'Object': {
1257
+ const props = node.properties.map(p => {
1258
+ if (p.type === 'spread') return `...${this.expr(p.value)}`;
1259
+ if (p.type === 'shorthand') return `'${p.key}': ${p.key}`;
1260
+ const key = typeof p.key === 'string' ? `'${p.key}'` :
1261
+ (p.key.type === 'String' ? this.generateString(p.key.value) :
1262
+ (p.key.type === 'Computed' ? this.expr(p.key.expr) : `'${this.expr(p.key)}'`));
1263
+ return `${key}: ${this.expr(p.value)}`;
1264
+ }).join(', ');
1265
+ return `{${props}}`;
1266
+ }
1267
+
1268
+ case 'ArrowFn': {
1269
+ const params = node.params.map(p => p.name).join(', ');
1270
+ const body = this.expr(node.body);
1271
+ return `(${params}) => ${body}`;
1272
+ }
1273
+
1274
+ case 'Lambda': {
1275
+ const async = node.isAsync ? ' async' : '';
1276
+ const params = node.params.map(p => p.name).join(', ');
1277
+ const savedOutput = this.mainBody;
1278
+ const savedIndent = this.indent;
1279
+ this.mainBody = [];
1280
+ this.indent = 0;
1281
+ const wasInFunction = this.inFunction;
1282
+ this.inFunction = true;
1283
+ for (const stmt of node.body) this.visitStatement(stmt);
1284
+ const bodyLines = this.mainBody;
1285
+ this.mainBody = savedOutput;
1286
+ this.indent = savedIndent;
1287
+ this.inFunction = wasInFunction;
1288
+ return `(${params})${async} { ${bodyLines.join(' ')} }`;
1289
+ }
1290
+
1291
+ case 'Ternary':
1292
+ return `(${this.expr(node.condition)} ? ${this.expr(node.consequent)} : ${this.expr(node.alternate)})`;
1293
+
1294
+ case 'Pipe':
1295
+ return this.generatePipe(node);
1296
+
1297
+ default:
1298
+ return `null /* expr:${node.type} */`;
1299
+ }
1300
+ }
1301
+
1302
+ mapIdentifier(name) {
1303
+ const map = {
1304
+ 'null': 'null',
1305
+ 'undefined': 'null',
1306
+ 'true': 'true',
1307
+ 'false': 'false',
1308
+ 'this': 'this',
1309
+ 'console': 'print',
1310
+ };
1311
+ return map[name] || name;
1312
+ }
1313
+
1314
+ mapBinaryOp(op) {
1315
+ const map = {
1316
+ '===': '==',
1317
+ '!==': '!=',
1318
+ '&&': '&&',
1319
+ '||': '||',
1320
+ 'and': '&&',
1321
+ 'or': '||',
1322
+ };
1323
+ return map[op] || op;
1324
+ }
1325
+
1326
+ mapUnaryOp(op) {
1327
+ if (op === 'not') return '!';
1328
+ return op;
1329
+ }
1330
+
1331
+ mapMemberAccess(node) {
1332
+ const obj = this.expr(node.object);
1333
+ const prop = node.property;
1334
+
1335
+ if (prop === 'length') return `${obj}.length`;
1336
+ if (prop === 'toString') return `${obj}.toString`;
1337
+
1338
+ if ((obj === 'request' || obj === 'req') && prop === 'body') return 'request.body';
1339
+ if ((obj === 'request' || obj === 'req') && prop === 'query') return 'request.uri.queryParameters';
1340
+ if ((obj === 'request' || obj === 'req') && prop === 'params') return 'request.params';
1341
+ if ((obj === 'request' || obj === 'req') && prop === 'headers') return 'request.headers';
1342
+ if ((obj === 'request' || obj === 'req') && prop === 'method') return 'request.method';
1343
+ if ((obj === 'request' || obj === 'req') && prop === 'url') return 'request.uri.toString()';
1344
+
1345
+ if (obj === 'JSON' && prop === 'parse') return 'jsonDecode';
1346
+ if (obj === 'JSON' && prop === 'stringify') return 'jsonEncode';
1347
+
1348
+ if (obj === 'Math' && prop === 'floor') return 'floor';
1349
+ if (obj === 'Math' && prop === 'ceil') return 'ceil';
1350
+ if (obj === 'Math' && prop === 'round') return 'round';
1351
+ if (obj === 'Math' && prop === 'abs') return 'abs';
1352
+ if (obj === 'Math' && prop === 'min') { this.addImport("import 'dart:math';"); return 'min'; }
1353
+ if (obj === 'Math' && prop === 'max') { this.addImport("import 'dart:math';"); return 'max'; }
1354
+ if (obj === 'Math' && prop === 'random') { this.addImport("import 'dart:math';"); return 'Random().nextDouble'; }
1355
+ if (obj === 'Math' && prop === 'PI') { this.addImport("import 'dart:math';"); return 'pi'; }
1356
+
1357
+ if (obj === 'console' && prop === 'log') return 'print';
1358
+ if (obj === 'console' && prop === 'error') return 'print';
1359
+ if (obj === 'console' && prop === 'warn') return 'print';
1360
+
1361
+ return `${obj}.${prop}`;
1362
+ }
1363
+
1364
+ generateCall(node) {
1365
+ const args = node.args.map(a => this.expr(a)).join(', ');
1366
+
1367
+ if (node.callee.type === 'Identifier') {
1368
+ const name = node.callee.name;
1369
+ if (name === 'parseInt') return `int.parse(${args})`;
1370
+ if (name === 'parseFloat') return `double.parse(${args})`;
1371
+ if (name === 'String') return `${args}.toString()`;
1372
+ if (name === 'Number') return `num.parse(${args})`;
1373
+ if (name === 'Boolean') return `(${args} == true)`;
1374
+ if (name === 'isNaN') return `${args}.isNaN`;
1375
+ if (name === 'setTimeout') return `Future.delayed(Duration(milliseconds: ${node.args.length > 1 ? this.expr(node.args[1]) : '0'}), ${this.expr(node.args[0])})`;
1376
+ if (name === 'setInterval') return `Timer.periodic(Duration(milliseconds: ${node.args.length > 1 ? this.expr(node.args[1]) : '1000'}), (_) => ${this.expr(node.args[0])}())`;
1377
+ }
1378
+
1379
+ if (node.callee.type === 'MemberAccess') {
1380
+ const obj = this.expr(node.callee.object);
1381
+ const prop = node.callee.property;
1382
+
1383
+ // List methods
1384
+ if (prop === 'push') return `${obj}.add(${args})`;
1385
+ if (prop === 'pop') return `${obj}.removeLast()`;
1386
+ if (prop === 'shift') return `${obj}.removeAt(0)`;
1387
+ if (prop === 'unshift') return `${obj}.insert(0, ${args})`;
1388
+ if (prop === 'indexOf') return `${obj}.indexOf(${args})`;
1389
+ if (prop === 'includes') return `${obj}.contains(${args})`;
1390
+ if (prop === 'join') return `${obj}.join(${args})`;
1391
+ if (prop === 'slice') return `${obj}.sublist(${args})`;
1392
+ if (prop === 'forEach') return `${obj}.forEach(${args})`;
1393
+ if (prop === 'map') return `${obj}.map(${args}).toList()`;
1394
+ if (prop === 'filter') return `${obj}.where(${args}).toList()`;
1395
+ if (prop === 'find') return `${obj}.firstWhere(${args}, orElse: () => null)`;
1396
+ if (prop === 'some') return `${obj}.any(${args})`;
1397
+ if (prop === 'every') return `${obj}.every(${args})`;
1398
+ if (prop === 'reduce') return `${obj}.reduce(${args})`;
1399
+ if (prop === 'flat') return `${obj}.expand((x) => x).toList()`;
1400
+ if (prop === 'reverse') return `${obj}.reversed.toList()`;
1401
+ if (prop === 'sort') return `(List.from(${obj})..sort(${args}))`;
1402
+ if (prop === 'concat') return `[...${obj}, ...${args}]`;
1403
+
1404
+ // String methods
1405
+ if (prop === 'split') return `${obj}.split(${args})`;
1406
+ if (prop === 'trim') return `${obj}.trim()`;
1407
+ if (prop === 'trimStart') return `${obj}.trimLeft()`;
1408
+ if (prop === 'trimEnd') return `${obj}.trimRight()`;
1409
+ if (prop === 'toUpperCase') return `${obj}.toUpperCase()`;
1410
+ if (prop === 'toLowerCase') return `${obj}.toLowerCase()`;
1411
+ if (prop === 'startsWith') return `${obj}.startsWith(${args})`;
1412
+ if (prop === 'endsWith') return `${obj}.endsWith(${args})`;
1413
+ if (prop === 'replace') return `${obj}.replaceFirst(${args})`;
1414
+ if (prop === 'replaceAll') return `${obj}.replaceAll(${args})`;
1415
+ if (prop === 'padStart') return `${obj}.padLeft(${args})`;
1416
+ if (prop === 'padEnd') return `${obj}.padRight(${args})`;
1417
+ if (prop === 'charAt') return `${obj}[${args}]`;
1418
+ if (prop === 'substring') return `${obj}.substring(${args})`;
1419
+ if (prop === 'repeat') return `${obj} * ${args}`;
1420
+
1421
+ // Object/Map methods
1422
+ if (obj === 'Object' && prop === 'keys') return `${this.expr(node.args[0])}.keys.toList()`;
1423
+ if (obj === 'Object' && prop === 'values') return `${this.expr(node.args[0])}.values.toList()`;
1424
+ if (obj === 'Object' && prop === 'entries') return `${this.expr(node.args[0])}.entries.toList()`;
1425
+
1426
+ // Array static
1427
+ if (obj === 'Array' && prop === 'isArray') return `${args} is List`;
1428
+ if (obj === 'Array' && prop === 'from') return `List.from(${args})`;
1429
+
1430
+ // Promise
1431
+ if (obj === 'Promise' && prop === 'all') return `Future.wait(${args})`;
1432
+ if (obj === 'Promise' && prop === 'resolve') return `Future.value(${args})`;
1433
+
1434
+ // Date
1435
+ if (obj === 'Date' && prop === 'now') return `DateTime.now().millisecondsSinceEpoch`;
1436
+
1437
+ // JSON
1438
+ if (prop === 'json' && (obj === 'request' || obj === 'req')) {
1439
+ this.addImport("import 'dart:convert';");
1440
+ return `jsonDecode(await utf8.decoder.bind(request).join())`;
1441
+ }
1442
+ }
1443
+
1444
+ const callee = this.expr(node.callee);
1445
+ return `${callee}(${args})`;
1446
+ }
1447
+
1448
+ generateString(strData) {
1449
+ if (!strData || !strData.parts) return "''";
1450
+
1451
+ const hasInterpolation = strData.parts.some(p => p.type === 'expr');
1452
+
1453
+ if (!hasInterpolation) {
1454
+ const raw = strData.parts.map(p => p.value).join('');
1455
+ return `'${raw.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
1456
+ }
1457
+
1458
+ let result = "'";
1459
+ for (const part of strData.parts) {
1460
+ if (part.type === 'text') {
1461
+ result += part.value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\$/g, '\\$');
1462
+ } else {
1463
+ const exprCode = part.value;
1464
+ if (/^\w+$/.test(exprCode)) {
1465
+ result += '$' + exprCode;
1466
+ } else {
1467
+ result += '${' + exprCode + '}';
1468
+ }
1469
+ }
1470
+ }
1471
+ result += "'";
1472
+ return result;
1473
+ }
1474
+
1475
+ rawString(strData) {
1476
+ if (!strData) return '';
1477
+ if (strData.raw !== null && strData.raw !== undefined) return strData.raw;
1478
+ if (strData.parts) return strData.parts.map(p => p.value).join('');
1479
+ return '';
1480
+ }
1481
+
1482
+ stringValue(strData) {
1483
+ if (!strData || !strData.parts) return "''";
1484
+ if (strData.raw !== null && strData.raw !== undefined) {
1485
+ return `'${strData.raw.replace(/'/g, "\\'")}'`;
1486
+ }
1487
+ return this.generateString(strData);
1488
+ }
1489
+
1490
+ generatePipe(node) {
1491
+ const steps = [];
1492
+ let current = node;
1493
+ while (current.type === 'Pipe') {
1494
+ steps.unshift(current.right);
1495
+ current = current.left;
1496
+ }
1497
+ steps.unshift(current);
1498
+
1499
+ const LIST_METHODS = new Set(['filter', 'map', 'reduce', 'find', 'some', 'every', 'sort', 'reverse', 'join']);
1500
+
1501
+ let result = this.expr(steps[0]);
1502
+ for (let i = 1; i < steps.length; i++) {
1503
+ const step = steps[i];
1504
+ if (step.type === 'Call' && step.callee.type === 'Identifier') {
1505
+ const name = step.callee.name;
1506
+ const args = step.args.map(a => this.expr(a)).join(', ');
1507
+
1508
+ if (name === 'filter') {
1509
+ result = `${result}.where(${args}).toList()`;
1510
+ } else if (name === 'map') {
1511
+ result = `${result}.map(${args}).toList()`;
1512
+ } else if (name === 'reduce') {
1513
+ result = `${result}.reduce(${args})`;
1514
+ } else if (name === 'sort') {
1515
+ result = `(List.from(${result})..sort())`;
1516
+ } else if (name === 'reverse') {
1517
+ result = `${result}.reversed.toList()`;
1518
+ } else if (name === 'join') {
1519
+ result = `${result}.join(${args})`;
1520
+ } else if (name === 'find') {
1521
+ result = `${result}.firstWhere(${args}, orElse: () => null)`;
1522
+ } else if (name === 'some') {
1523
+ result = `${result}.any(${args})`;
1524
+ } else if (name === 'every') {
1525
+ result = `${result}.every(${args})`;
1526
+ } else {
1527
+ result = `${name}(${result}${args ? ', ' + args : ''})`;
1528
+ }
1529
+ } else if (step.type === 'Identifier') {
1530
+ result = `${step.name}(${result})`;
1531
+ } else if (step.type === 'Call') {
1532
+ const callee = this.expr(step.callee);
1533
+ const args = step.args.map(a => this.expr(a)).join(', ');
1534
+ result = `${callee}(${result}${args ? ', ' + args : ''})`;
1535
+ } else {
1536
+ result = `(${this.expr(step)})(${result})`;
1537
+ }
1538
+ }
1539
+ return result;
1540
+ }
1541
+
1542
+ bodyUsesAwait(body) {
1543
+ for (const stmt of body) {
1544
+ if (this.stmtUsesAwait(stmt)) return true;
1545
+ }
1546
+ return false;
1547
+ }
1548
+
1549
+ stmtUsesAwait(node) {
1550
+ if (!node) return false;
1551
+ const json = JSON.stringify(node);
1552
+ return json.includes('"Await"') || json.includes('"AwaitAll"');
1553
+ }
1554
+
1555
+ statementToString(stmt) {
1556
+ const saved = this.mainBody;
1557
+ const savedIndent = this.indent;
1558
+ this.mainBody = [];
1559
+ this.indent = 0;
1560
+ const wasInFunction = this.inFunction;
1561
+ this.inFunction = true;
1562
+ this.visitStatement(stmt);
1563
+ const result = this.mainBody.join('\n');
1564
+ this.mainBody = saved;
1565
+ this.indent = savedIndent;
1566
+ this.inFunction = wasInFunction;
1567
+ return result;
1568
+ }
1569
+ }