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