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.
- package/README.md +209 -8
- package/SPEC.naide +152 -0
- package/lsp/server.js +55 -0
- package/package.json +22 -3
- package/src/generator-c.js +1014 -0
- package/src/generator-cpp.js +1211 -0
- package/src/generator-csharp.js +1633 -0
- package/src/generator-dart.js +1569 -0
- package/src/generator-go.js +1781 -0
- package/src/generator-java.js +1814 -0
- package/src/generator-kotlin.js +1819 -0
- package/src/generator-php.js +1576 -0
- package/src/generator-python.js +291 -0
- package/src/generator-ruby.js +1530 -0
- package/src/generator-rust.js +1887 -0
- package/src/generator-swift.js +1735 -0
- package/src/generator-typescript.js +126 -0
- package/src/generator.js +296 -0
- package/src/index.js +55 -0
- package/src/parser.js +308 -3
- package/src/tokens.js +27 -0
- package/vscode-naide/syntaxes/naide.tmLanguage.json +7 -3
|
@@ -0,0 +1,1814 @@
|
|
|
1
|
+
export class JavaGenerator {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
this.indent = 0;
|
|
4
|
+
this.output = [];
|
|
5
|
+
this.imports = new Set();
|
|
6
|
+
this.className = options.className || 'App';
|
|
7
|
+
this.sourceMap = [];
|
|
8
|
+
this.currentSourceLine = 0;
|
|
9
|
+
this.sourceFile = options.sourceFile || null;
|
|
10
|
+
this.inClass = false;
|
|
11
|
+
this.models = new Map();
|
|
12
|
+
this.schemas = new Map();
|
|
13
|
+
this.authSecret = null;
|
|
14
|
+
this.hasMain = false;
|
|
15
|
+
this.mainBody = [];
|
|
16
|
+
this.topLevelMethods = [];
|
|
17
|
+
this.topLevelFields = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
generate(ast) {
|
|
21
|
+
// First pass: collect all statements
|
|
22
|
+
this.visitProgram(ast);
|
|
23
|
+
|
|
24
|
+
// Build final output with imports, class wrapper, and main method
|
|
25
|
+
const preamble = [];
|
|
26
|
+
for (const imp of this.imports) {
|
|
27
|
+
preamble.push(`import ${imp};`);
|
|
28
|
+
}
|
|
29
|
+
if (this.imports.size > 0) preamble.push('');
|
|
30
|
+
|
|
31
|
+
this.output.unshift(...preamble);
|
|
32
|
+
|
|
33
|
+
return this.output.join('\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
emit(line) {
|
|
37
|
+
this.output.push(' '.repeat(this.indent) + line);
|
|
38
|
+
this.sourceMap.push(this.currentSourceLine);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
emitRaw(line) {
|
|
42
|
+
this.output.push(line);
|
|
43
|
+
this.sourceMap.push(this.currentSourceLine);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
addImport(imp) {
|
|
47
|
+
this.imports.add(imp);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ===== Type Mapping =====
|
|
51
|
+
|
|
52
|
+
mapType(naideType) {
|
|
53
|
+
const map = {
|
|
54
|
+
'str': 'String',
|
|
55
|
+
'string': 'String',
|
|
56
|
+
'int': 'int',
|
|
57
|
+
'integer': 'int',
|
|
58
|
+
'num': 'double',
|
|
59
|
+
'number': 'double',
|
|
60
|
+
'float': 'double',
|
|
61
|
+
'bool': 'boolean',
|
|
62
|
+
'boolean': 'boolean',
|
|
63
|
+
'list': 'List<Object>',
|
|
64
|
+
'map': 'Map<String, Object>',
|
|
65
|
+
'json': 'Object',
|
|
66
|
+
'any': 'Object',
|
|
67
|
+
'void': 'void',
|
|
68
|
+
'auto': 'int',
|
|
69
|
+
'timestamp': 'String',
|
|
70
|
+
};
|
|
71
|
+
return map[naideType] || 'Object';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
mapTypeBoxed(naideType) {
|
|
75
|
+
const map = {
|
|
76
|
+
'str': 'String',
|
|
77
|
+
'string': 'String',
|
|
78
|
+
'int': 'Integer',
|
|
79
|
+
'integer': 'Integer',
|
|
80
|
+
'num': 'Double',
|
|
81
|
+
'number': 'Double',
|
|
82
|
+
'float': 'Double',
|
|
83
|
+
'bool': 'Boolean',
|
|
84
|
+
'boolean': 'Boolean',
|
|
85
|
+
'list': 'List<Object>',
|
|
86
|
+
'map': 'Map<String, Object>',
|
|
87
|
+
'json': 'Object',
|
|
88
|
+
'any': 'Object',
|
|
89
|
+
'void': 'Void',
|
|
90
|
+
'auto': 'Integer',
|
|
91
|
+
'timestamp': 'String',
|
|
92
|
+
};
|
|
93
|
+
return map[naideType] || 'Object';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ===== Program =====
|
|
97
|
+
|
|
98
|
+
visitProgram(node) {
|
|
99
|
+
// Wrap everything in a class
|
|
100
|
+
this.emit(`public class ${this.className} {`);
|
|
101
|
+
this.indent++;
|
|
102
|
+
this.inClass = true;
|
|
103
|
+
|
|
104
|
+
let hasMainContent = false;
|
|
105
|
+
const mainStatements = [];
|
|
106
|
+
const classLevelStatements = [];
|
|
107
|
+
|
|
108
|
+
for (const stmt of node.body) {
|
|
109
|
+
if (this.isClassLevel(stmt)) {
|
|
110
|
+
classLevelStatements.push(stmt);
|
|
111
|
+
} else {
|
|
112
|
+
mainStatements.push(stmt);
|
|
113
|
+
hasMainContent = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Emit class-level declarations first (functions, models)
|
|
118
|
+
for (const stmt of classLevelStatements) {
|
|
119
|
+
this.visitStatement(stmt);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Wrap remaining statements in main method
|
|
123
|
+
if (hasMainContent) {
|
|
124
|
+
this.emitRaw('');
|
|
125
|
+
this.emit('public static void main(String[] args) {');
|
|
126
|
+
this.indent++;
|
|
127
|
+
for (const stmt of mainStatements) {
|
|
128
|
+
this.visitStatement(stmt);
|
|
129
|
+
}
|
|
130
|
+
this.indent--;
|
|
131
|
+
this.emit('}');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.indent--;
|
|
135
|
+
this.emit('}');
|
|
136
|
+
this.inClass = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
isClassLevel(node) {
|
|
140
|
+
return node.type === 'Function' ||
|
|
141
|
+
node.type === 'Model' ||
|
|
142
|
+
node.type === 'Server' ||
|
|
143
|
+
node.type === 'Bot' ||
|
|
144
|
+
node.type === 'SchemaDecl' ||
|
|
145
|
+
node.type === 'Use' ||
|
|
146
|
+
node.type === 'UseDestructured';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ===== Statement Dispatcher =====
|
|
150
|
+
|
|
151
|
+
visitStatement(node) {
|
|
152
|
+
if (node._line) this.currentSourceLine = node._line;
|
|
153
|
+
switch (node.type) {
|
|
154
|
+
case 'Use': return this.visitUse(node);
|
|
155
|
+
case 'UseDestructured': return this.visitUseDestructured(node);
|
|
156
|
+
case 'Function': return this.visitFunction(node);
|
|
157
|
+
case 'Return': return this.visitReturn(node);
|
|
158
|
+
case 'ReturnStatus': return this.visitReturnStatus(node);
|
|
159
|
+
case 'ReturnMethod': return this.visitReturnMethod(node);
|
|
160
|
+
case 'TypedVar': return this.visitTypedVar(node);
|
|
161
|
+
case 'If': return this.visitIf(node);
|
|
162
|
+
case 'Each': return this.visitEach(node);
|
|
163
|
+
case 'For': return this.visitFor(node);
|
|
164
|
+
case 'While': return this.visitWhile(node);
|
|
165
|
+
case 'Match': return this.visitMatch(node);
|
|
166
|
+
case 'Try': return this.visitTry(node);
|
|
167
|
+
case 'Server': return this.visitServer(node);
|
|
168
|
+
case 'Bot': return this.visitBot(node);
|
|
169
|
+
case 'Model': return this.visitModel(node);
|
|
170
|
+
case 'On': return this.visitOn(node);
|
|
171
|
+
case 'Log': return this.visitLog(node);
|
|
172
|
+
case 'Throw': return this.visitThrow(node);
|
|
173
|
+
case 'Break': this.emit('break;'); return;
|
|
174
|
+
case 'Continue': this.emit('continue;'); return;
|
|
175
|
+
case 'Assignment': return this.visitAssignment(node);
|
|
176
|
+
case 'CompoundAssign': return this.visitCompoundAssign(node);
|
|
177
|
+
case 'ExprStatement': this.emit(this.expr(node.expression) + ';'); return;
|
|
178
|
+
case 'DbConnect': return this.visitDbConnect(node);
|
|
179
|
+
case 'DbDir': return this.visitDbDir(node);
|
|
180
|
+
case 'DbSql': return this.visitDbSql(node);
|
|
181
|
+
case 'AwaitAll': return this.visitAwaitAllStatement(node);
|
|
182
|
+
case 'SchemaDecl': return this.visitSchema(node);
|
|
183
|
+
case 'CrudDecl': return this.visitCrud(node);
|
|
184
|
+
case 'AuthDecl': return this.visitAuth(node);
|
|
185
|
+
case 'CorsDecl': return this.visitCors(node);
|
|
186
|
+
case 'LimitDecl': return this.visitLimit(node);
|
|
187
|
+
case 'EnvDecl': return this.visitEnv(node);
|
|
188
|
+
case 'EveryDecl': return this.visitEvery(node);
|
|
189
|
+
case 'WatchDecl': return this.visitWatch(node);
|
|
190
|
+
case 'StaticDecl': return this.visitStatic(node);
|
|
191
|
+
case 'WsDecl': return this.visitWs(node);
|
|
192
|
+
case 'GroupDecl': return this.visitGroup(node);
|
|
193
|
+
case 'ErrorHandler': return this.visitErrorHandler(node);
|
|
194
|
+
case 'CookieDecl': return this.visitCookie(node);
|
|
195
|
+
case 'UploadDecl': return this.visitUpload(node);
|
|
196
|
+
case 'SessionDecl': return this.visitSession(node);
|
|
197
|
+
case 'ViewDecl': return this.visitView(node);
|
|
198
|
+
case 'SseDecl': return this.visitSse(node);
|
|
199
|
+
case 'CacheDecl': return this.visitCache(node);
|
|
200
|
+
case 'MiddlewareRef': return this.visitMiddlewareRef(node);
|
|
201
|
+
case 'ReturnRender': return this.visitReturnRender(node);
|
|
202
|
+
case 'ValidateDecl': return this.visitValidate(node);
|
|
203
|
+
case 'TestDecl': return this.visitTest(node);
|
|
204
|
+
case 'AssertStmt': return this.visitAssert(node);
|
|
205
|
+
case 'QueueDecl': return this.visitQueue(node);
|
|
206
|
+
case 'OpenapiDecl': return this.visitOpenapi(node);
|
|
207
|
+
case 'ReturnRedirect': return this.visitReturnRedirect(node);
|
|
208
|
+
case 'ReturnDownload': return this.visitReturnDownload(node);
|
|
209
|
+
case 'PromptDecl': return this.visitPrompt(node);
|
|
210
|
+
case 'Page': return this.visitPage(node);
|
|
211
|
+
case 'CliApp': return this.visitCli(node);
|
|
212
|
+
case 'MailConfig': return this.visitMail(node);
|
|
213
|
+
case 'DesktopApp': return this.visitDesktop(node);
|
|
214
|
+
case 'Screen': return this.visitScreen(node);
|
|
215
|
+
case 'OauthDecl': return this.visitOauth(node);
|
|
216
|
+
case 'PayDecl': return this.visitPay(node);
|
|
217
|
+
case 'StorageDecl': return this.visitStorage(node);
|
|
218
|
+
case 'PdfDecl': return this.visitPdf(node);
|
|
219
|
+
case 'I18nDecl': return this.visitI18n(node);
|
|
220
|
+
case 'PushDecl': return this.visitPush(node);
|
|
221
|
+
case 'SearchDecl': return this.visitSearch(node);
|
|
222
|
+
case 'ImageDecl': return this.visitImage(node);
|
|
223
|
+
case 'CsvDecl': return this.visitCsv(node);
|
|
224
|
+
case 'LoggingDecl': return this.visitLogging(node);
|
|
225
|
+
case 'MigrateDecl': return this.visitMigrate(node);
|
|
226
|
+
case 'GrpcDecl': return this.visitGrpc(node);
|
|
227
|
+
case 'WebrtcDecl': return this.visitWebrtc(node);
|
|
228
|
+
case 'BlockchainDecl': return this.visitBlockchain(node);
|
|
229
|
+
default:
|
|
230
|
+
this.emit(`/* unknown: ${node.type} */`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ===== Imports =====
|
|
235
|
+
|
|
236
|
+
visitUse(node) {
|
|
237
|
+
const raw = node.source ? this.rawString(node.source) : node.name;
|
|
238
|
+
const javaImport = this.mapModuleName(raw);
|
|
239
|
+
if (javaImport) {
|
|
240
|
+
this.addImport(javaImport);
|
|
241
|
+
}
|
|
242
|
+
this.emit(`// use ${raw}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
visitUseDestructured(node) {
|
|
246
|
+
const raw = this.rawString(node.source);
|
|
247
|
+
const names = node.names.map(n => n.name).join(', ');
|
|
248
|
+
const javaImport = this.mapModuleName(raw);
|
|
249
|
+
if (javaImport) {
|
|
250
|
+
this.addImport(javaImport);
|
|
251
|
+
}
|
|
252
|
+
this.emit(`// use { ${names} } from ${raw}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
mapModuleName(name) {
|
|
256
|
+
const clean = name.replace(/^['"]|['"]$/g, '');
|
|
257
|
+
const map = {
|
|
258
|
+
'express': 'com.sun.net.httpserver.*',
|
|
259
|
+
'fs': 'java.io.*',
|
|
260
|
+
'path': 'java.nio.file.*',
|
|
261
|
+
'crypto': 'java.security.*',
|
|
262
|
+
'http': 'java.net.http.*',
|
|
263
|
+
'uuid': 'java.util.UUID',
|
|
264
|
+
'axios': 'java.net.http.*',
|
|
265
|
+
};
|
|
266
|
+
return map[clean] || null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ===== Functions =====
|
|
270
|
+
|
|
271
|
+
visitFunction(node) {
|
|
272
|
+
const params = node.params.map(p => {
|
|
273
|
+
let s = '';
|
|
274
|
+
if (p.spread) s += 'Object... ';
|
|
275
|
+
else s += 'Object ';
|
|
276
|
+
s += p.name;
|
|
277
|
+
return s;
|
|
278
|
+
}).join(', ');
|
|
279
|
+
|
|
280
|
+
const returnType = node.returnType ? this.mapType(node.returnType) : 'Object';
|
|
281
|
+
const modifier = this.inClass ? 'public static' : 'public';
|
|
282
|
+
|
|
283
|
+
this.emit(`${modifier} ${returnType} ${node.name}(${params}) {`);
|
|
284
|
+
this.indent++;
|
|
285
|
+
if (node.body.length === 0) {
|
|
286
|
+
if (returnType !== 'void') this.emit('return null;');
|
|
287
|
+
} else {
|
|
288
|
+
for (const stmt of node.body) {
|
|
289
|
+
this.visitStatement(stmt);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
this.indent--;
|
|
293
|
+
this.emit('}');
|
|
294
|
+
this.emitRaw('');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
visitReturn(node) {
|
|
298
|
+
if (node.value === null) {
|
|
299
|
+
this.emit('return;');
|
|
300
|
+
} else {
|
|
301
|
+
this.emit(`return ${this.expr(node.value)};`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
visitReturnStatus(node) {
|
|
306
|
+
this.emit(`// return status ${this.expr(node.statusCode)}: ${this.expr(node.body)}`);
|
|
307
|
+
this.emit(`exchange.sendResponseHeaders(${this.expr(node.statusCode)}, 0);`);
|
|
308
|
+
this.emit(`try (var os = exchange.getResponseBody()) { os.write(${this.expr(node.body)}.toString().getBytes()); }`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
visitReturnMethod(node) {
|
|
312
|
+
const val = this.expr(node.value);
|
|
313
|
+
switch (node.method) {
|
|
314
|
+
case 'redirect':
|
|
315
|
+
this.emit(`exchange.getResponseHeaders().set("Location", ${val});`);
|
|
316
|
+
this.emit(`exchange.sendResponseHeaders(302, -1);`);
|
|
317
|
+
break;
|
|
318
|
+
case 'html':
|
|
319
|
+
this.emit(`exchange.getResponseHeaders().set("Content-Type", "text/html");`);
|
|
320
|
+
this.emit(`exchange.sendResponseHeaders(200, 0);`);
|
|
321
|
+
this.emit(`try (var os = exchange.getResponseBody()) { os.write(${val}.toString().getBytes()); }`);
|
|
322
|
+
break;
|
|
323
|
+
case 'text':
|
|
324
|
+
this.emit(`exchange.getResponseHeaders().set("Content-Type", "text/plain");`);
|
|
325
|
+
this.emit(`exchange.sendResponseHeaders(200, 0);`);
|
|
326
|
+
this.emit(`try (var os = exchange.getResponseBody()) { os.write(${val}.toString().getBytes()); }`);
|
|
327
|
+
break;
|
|
328
|
+
case 'file':
|
|
329
|
+
this.emit(`// sendFile: ${val}`);
|
|
330
|
+
this.emit(`var __fileBytes = java.nio.file.Files.readAllBytes(java.nio.file.Path.of(${val}));`);
|
|
331
|
+
this.emit(`exchange.sendResponseHeaders(200, __fileBytes.length);`);
|
|
332
|
+
this.emit(`try (var os = exchange.getResponseBody()) { os.write(__fileBytes); }`);
|
|
333
|
+
break;
|
|
334
|
+
default:
|
|
335
|
+
this.emit(`// return ${node.method}: ${val}`);
|
|
336
|
+
this.emit(`exchange.sendResponseHeaders(200, 0);`);
|
|
337
|
+
this.emit(`try (var os = exchange.getResponseBody()) { os.write(${val}.toString().getBytes()); }`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ===== Variables =====
|
|
342
|
+
|
|
343
|
+
visitTypedVar(node) {
|
|
344
|
+
const javaType = node.naideType ? this.mapType(node.naideType) : 'var';
|
|
345
|
+
this.emit(`${javaType} ${node.name} = ${this.expr(node.value)};`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
visitAssignment(node) {
|
|
349
|
+
const value = this.expr(node.value);
|
|
350
|
+
|
|
351
|
+
if (node.target.type === 'Array') {
|
|
352
|
+
// Java doesn't have destructuring; emit indexed assignments
|
|
353
|
+
const tempVar = '__arr';
|
|
354
|
+
this.emit(`var ${tempVar} = ${value};`);
|
|
355
|
+
node.target.elements.forEach((e, i) => {
|
|
356
|
+
this.emit(`var ${this.expr(e)} = ${tempVar}[${i}];`);
|
|
357
|
+
});
|
|
358
|
+
} else if (node.target.type === 'Object') {
|
|
359
|
+
const tempVar = '__map';
|
|
360
|
+
this.emit(`var ${tempVar} = ${value};`);
|
|
361
|
+
for (const p of node.target.properties) {
|
|
362
|
+
const key = p.type === 'shorthand' ? p.key : p.key;
|
|
363
|
+
const varName = p.type === 'shorthand' ? p.key : this.expr(p.value);
|
|
364
|
+
this.emit(`var ${varName} = ${tempVar}.get(${JSON.stringify(key)});`);
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
this.emit(`${this.expr(node.target)} = ${value};`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
visitCompoundAssign(node) {
|
|
372
|
+
this.emit(`${this.expr(node.target)} ${node.op} ${this.expr(node.value)};`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ===== Control Flow =====
|
|
376
|
+
|
|
377
|
+
visitIf(node) {
|
|
378
|
+
this.emit(`if (${this.expr(node.condition)}) {`);
|
|
379
|
+
this.indent++;
|
|
380
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
381
|
+
this.indent--;
|
|
382
|
+
|
|
383
|
+
for (const elif of node.elifs) {
|
|
384
|
+
this.emit(`} else if (${this.expr(elif.condition)}) {`);
|
|
385
|
+
this.indent++;
|
|
386
|
+
for (const stmt of elif.body) this.visitStatement(stmt);
|
|
387
|
+
this.indent--;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (node.elseBody) {
|
|
391
|
+
this.emit('} else {');
|
|
392
|
+
this.indent++;
|
|
393
|
+
for (const stmt of node.elseBody) this.visitStatement(stmt);
|
|
394
|
+
this.indent--;
|
|
395
|
+
}
|
|
396
|
+
this.emit('}');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
visitEach(node) {
|
|
400
|
+
const collection = this.expr(node.collection);
|
|
401
|
+
if (node.key) {
|
|
402
|
+
this.addImport('java.util.Map');
|
|
403
|
+
this.emit(`for (var __entry : ((Map<?, ?>) ${collection}).entrySet()) {`);
|
|
404
|
+
this.indent++;
|
|
405
|
+
this.emit(`var ${node.key} = __entry.getKey();`);
|
|
406
|
+
this.emit(`var ${node.value} = __entry.getValue();`);
|
|
407
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
408
|
+
this.indent--;
|
|
409
|
+
this.emit('}');
|
|
410
|
+
} else {
|
|
411
|
+
this.emit(`for (var ${node.value} : ${collection}) {`);
|
|
412
|
+
this.indent++;
|
|
413
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
414
|
+
this.indent--;
|
|
415
|
+
this.emit('}');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
visitFor(node) {
|
|
420
|
+
const varName = node.varName;
|
|
421
|
+
const start = this.expr(node.start);
|
|
422
|
+
const end = this.expr(node.end);
|
|
423
|
+
this.emit(`for (int ${varName} = ${start}; ${varName} < ${end}; ${varName}++) {`);
|
|
424
|
+
this.indent++;
|
|
425
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
426
|
+
this.indent--;
|
|
427
|
+
this.emit('}');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
visitWhile(node) {
|
|
431
|
+
this.emit(`while (${this.expr(node.condition)}) {`);
|
|
432
|
+
this.indent++;
|
|
433
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
434
|
+
this.indent--;
|
|
435
|
+
this.emit('}');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
visitMatch(node) {
|
|
439
|
+
this.emit(`switch (${this.expr(node.value)}) {`);
|
|
440
|
+
this.indent++;
|
|
441
|
+
for (const c of node.cases) {
|
|
442
|
+
if (c.pattern.type === 'DefaultPattern') {
|
|
443
|
+
this.emit('default: {');
|
|
444
|
+
} else {
|
|
445
|
+
this.emit(`case ${this.expr(c.pattern)}: {`);
|
|
446
|
+
}
|
|
447
|
+
this.indent++;
|
|
448
|
+
for (const stmt of c.body) this.visitStatement(stmt);
|
|
449
|
+
this.emit('break;');
|
|
450
|
+
this.indent--;
|
|
451
|
+
this.emit('}');
|
|
452
|
+
}
|
|
453
|
+
this.indent--;
|
|
454
|
+
this.emit('}');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
visitTry(node) {
|
|
458
|
+
this.emit('try {');
|
|
459
|
+
this.indent++;
|
|
460
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
461
|
+
this.indent--;
|
|
462
|
+
if (node.catchBody) {
|
|
463
|
+
const catchParam = node.catchVar || 'e';
|
|
464
|
+
this.emit(`} catch (Exception ${catchParam}) {`);
|
|
465
|
+
this.indent++;
|
|
466
|
+
for (const stmt of node.catchBody) this.visitStatement(stmt);
|
|
467
|
+
this.indent--;
|
|
468
|
+
}
|
|
469
|
+
if (node.ensureBody) {
|
|
470
|
+
this.emit('} finally {');
|
|
471
|
+
this.indent++;
|
|
472
|
+
for (const stmt of node.ensureBody) this.visitStatement(stmt);
|
|
473
|
+
this.indent--;
|
|
474
|
+
}
|
|
475
|
+
this.emit('}');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ===== Server (HttpServer) =====
|
|
479
|
+
|
|
480
|
+
visitServer(node) {
|
|
481
|
+
this.addImport('com.sun.net.httpserver.HttpServer');
|
|
482
|
+
this.addImport('com.sun.net.httpserver.HttpExchange');
|
|
483
|
+
this.addImport('java.net.InetSocketAddress');
|
|
484
|
+
this.addImport('java.io.OutputStream');
|
|
485
|
+
this.addImport('java.io.InputStream');
|
|
486
|
+
this.addImport('java.nio.charset.StandardCharsets');
|
|
487
|
+
|
|
488
|
+
const port = node.port ? this.expr(node.port) : '3000';
|
|
489
|
+
|
|
490
|
+
this.emit(`HttpServer ${node.name} = HttpServer.create(new InetSocketAddress(${port}), 0);`);
|
|
491
|
+
this.emitRaw('');
|
|
492
|
+
|
|
493
|
+
for (const child of node.routes) {
|
|
494
|
+
if (child.type === 'Route') {
|
|
495
|
+
this.visitRoute(node.name, child);
|
|
496
|
+
} else if (child.type === 'CrudDecl') {
|
|
497
|
+
this.visitCrudRoute(node.name, child);
|
|
498
|
+
} else if (child.type === 'AuthDecl') {
|
|
499
|
+
this.visitAuth(child);
|
|
500
|
+
} else if (child.type === 'CorsDecl') {
|
|
501
|
+
this.visitCors(child);
|
|
502
|
+
} else if (child.type === 'StaticDecl') {
|
|
503
|
+
this.visitStaticRoute(node.name, child);
|
|
504
|
+
} else if (child.type === 'GroupDecl') {
|
|
505
|
+
this.visitGroupRoute(node.name, child);
|
|
506
|
+
} else if (child.type === 'ErrorHandler') {
|
|
507
|
+
this.visitErrorHandler(child);
|
|
508
|
+
} else if (child.type === 'SchemaDecl') {
|
|
509
|
+
this.visitSchema(child);
|
|
510
|
+
} else if (child.type === 'WsDecl') {
|
|
511
|
+
this.emit(`// TODO: WebSocket - use Java-WebSocket library`);
|
|
512
|
+
} else if (child.type === 'GraphqlDecl') {
|
|
513
|
+
this.emit(`// TODO: GraphQL - use graphql-java library`);
|
|
514
|
+
} else {
|
|
515
|
+
this.visitStatement(child);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
this.emitRaw('');
|
|
520
|
+
this.emit(`${node.name}.setExecutor(null);`);
|
|
521
|
+
this.emit(`${node.name}.start();`);
|
|
522
|
+
this.emit(`System.out.println("Server running on port " + ${port});`);
|
|
523
|
+
this.emitRaw('');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
visitRoute(appName, route) {
|
|
527
|
+
const method = route.method === 'del' ? 'DELETE' : route.method.toUpperCase();
|
|
528
|
+
const path = this.rawString(route.path);
|
|
529
|
+
|
|
530
|
+
this.emit(`${appName}.createContext("${path}", exchange -> {`);
|
|
531
|
+
this.indent++;
|
|
532
|
+
this.emit(`if ("${method}".equals(exchange.getRequestMethod())) {`);
|
|
533
|
+
this.indent++;
|
|
534
|
+
|
|
535
|
+
// Read request body
|
|
536
|
+
this.emit(`String __reqBody = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);`);
|
|
537
|
+
|
|
538
|
+
for (let i = 0; i < route.body.length; i++) {
|
|
539
|
+
const stmt = route.body[i];
|
|
540
|
+
if (stmt.type === 'Return' && stmt.value !== null) {
|
|
541
|
+
this.emit(`String __response = String.valueOf(${this.expr(stmt.value)});`);
|
|
542
|
+
this.emit(`exchange.sendResponseHeaders(200, __response.getBytes().length);`);
|
|
543
|
+
this.emit(`try (OutputStream os = exchange.getResponseBody()) { os.write(__response.getBytes()); }`);
|
|
544
|
+
} else {
|
|
545
|
+
this.visitStatement(stmt);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.indent--;
|
|
550
|
+
this.emit('} else {');
|
|
551
|
+
this.indent++;
|
|
552
|
+
this.emit('exchange.sendResponseHeaders(405, -1);');
|
|
553
|
+
this.indent--;
|
|
554
|
+
this.emit('}');
|
|
555
|
+
this.emit('exchange.close();');
|
|
556
|
+
this.indent--;
|
|
557
|
+
this.emit('});');
|
|
558
|
+
this.emitRaw('');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
visitGroupRoute(appName, node) {
|
|
562
|
+
const prefix = this.rawString(node.prefix);
|
|
563
|
+
this.emit(`// Group: ${prefix}`);
|
|
564
|
+
for (const child of node.routes) {
|
|
565
|
+
if (child.type === 'Route') {
|
|
566
|
+
// Prepend group prefix to route path
|
|
567
|
+
const origPath = this.rawString(child.path);
|
|
568
|
+
const fullPath = prefix + origPath;
|
|
569
|
+
const modifiedRoute = { ...child, path: { raw: fullPath, parts: [{ type: 'text', value: fullPath }] } };
|
|
570
|
+
this.visitRoute(appName, modifiedRoute);
|
|
571
|
+
} else {
|
|
572
|
+
this.visitStatement(child);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
visitCrudRoute(appName, node) {
|
|
578
|
+
const path = this.rawString(node.path);
|
|
579
|
+
const schema = node.schemaName;
|
|
580
|
+
this.addImport('java.util.ArrayList');
|
|
581
|
+
this.addImport('java.util.Map');
|
|
582
|
+
this.addImport('java.util.HashMap');
|
|
583
|
+
|
|
584
|
+
this.emit(`// CRUD for ${schema} at ${path}`);
|
|
585
|
+
this.emit(`var ${schema.toLowerCase()}Store = new ArrayList<Map<String, Object>>();`);
|
|
586
|
+
this.emit(`final int[] ${schema.toLowerCase()}IdCounter = {1};`);
|
|
587
|
+
this.emitRaw('');
|
|
588
|
+
|
|
589
|
+
this.emit(`${appName}.createContext("${path}", exchange -> {`);
|
|
590
|
+
this.indent++;
|
|
591
|
+
this.emit(`String method = exchange.getRequestMethod();`);
|
|
592
|
+
this.emit(`String __body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);`);
|
|
593
|
+
this.emit(`String response = "{}";`);
|
|
594
|
+
this.emit(`int status = 200;`);
|
|
595
|
+
this.emitRaw('');
|
|
596
|
+
this.emit(`if ("GET".equals(method)) {`);
|
|
597
|
+
this.indent++;
|
|
598
|
+
this.emit(`response = ${schema.toLowerCase()}Store.toString();`);
|
|
599
|
+
this.indent--;
|
|
600
|
+
this.emit(`} else if ("POST".equals(method)) {`);
|
|
601
|
+
this.indent++;
|
|
602
|
+
this.emit(`var item = new HashMap<String, Object>();`);
|
|
603
|
+
this.emit(`item.put("id", ${schema.toLowerCase()}IdCounter[0]++);`);
|
|
604
|
+
this.emit(`${schema.toLowerCase()}Store.add(item);`);
|
|
605
|
+
this.emit(`response = item.toString();`);
|
|
606
|
+
this.emit(`status = 201;`);
|
|
607
|
+
this.indent--;
|
|
608
|
+
this.emit(`} else if ("DELETE".equals(method)) {`);
|
|
609
|
+
this.indent++;
|
|
610
|
+
this.emit(`${schema.toLowerCase()}Store.clear();`);
|
|
611
|
+
this.emit(`response = "{\\"deleted\\": true}";`);
|
|
612
|
+
this.indent--;
|
|
613
|
+
this.emit('}');
|
|
614
|
+
this.emit(`exchange.sendResponseHeaders(status, response.getBytes().length);`);
|
|
615
|
+
this.emit(`try (OutputStream os = exchange.getResponseBody()) { os.write(response.getBytes()); }`);
|
|
616
|
+
this.emit('exchange.close();');
|
|
617
|
+
this.indent--;
|
|
618
|
+
this.emit('});');
|
|
619
|
+
this.emitRaw('');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
visitStaticRoute(appName, node) {
|
|
623
|
+
let raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'public';
|
|
624
|
+
if (raw.startsWith('/')) raw = raw.slice(1);
|
|
625
|
+
this.addImport('java.nio.file.Files');
|
|
626
|
+
this.addImport('java.nio.file.Path');
|
|
627
|
+
|
|
628
|
+
this.emit(`${appName}.createContext("/", exchange -> {`);
|
|
629
|
+
this.indent++;
|
|
630
|
+
this.emit(`String filePath = "${raw}" + exchange.getRequestURI().getPath();`);
|
|
631
|
+
this.emit(`Path path = Path.of(filePath);`);
|
|
632
|
+
this.emit(`if (Files.exists(path)) {`);
|
|
633
|
+
this.indent++;
|
|
634
|
+
this.emit(`byte[] bytes = Files.readAllBytes(path);`);
|
|
635
|
+
this.emit(`exchange.sendResponseHeaders(200, bytes.length);`);
|
|
636
|
+
this.emit(`try (OutputStream os = exchange.getResponseBody()) { os.write(bytes); }`);
|
|
637
|
+
this.indent--;
|
|
638
|
+
this.emit('} else {');
|
|
639
|
+
this.indent++;
|
|
640
|
+
this.emit('exchange.sendResponseHeaders(404, -1);');
|
|
641
|
+
this.indent--;
|
|
642
|
+
this.emit('}');
|
|
643
|
+
this.emit('exchange.close();');
|
|
644
|
+
this.indent--;
|
|
645
|
+
this.emit('});');
|
|
646
|
+
this.emitRaw('');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ===== Bot =====
|
|
650
|
+
|
|
651
|
+
visitBot(node) {
|
|
652
|
+
const tokenExpr = node.token ? this.expr(node.token) : 'System.getenv("BOT_TOKEN")';
|
|
653
|
+
const botType = this.rawBotType(node.botType);
|
|
654
|
+
this.emit(`// TODO: ${botType} bot - use JDA (Discord), TelegramBots, or Slack SDK for Java`);
|
|
655
|
+
this.emit(`// Token: ${tokenExpr}`);
|
|
656
|
+
this.emitRaw('');
|
|
657
|
+
|
|
658
|
+
for (const handler of node.handlers) {
|
|
659
|
+
if (handler.type === 'BotEvent') {
|
|
660
|
+
const evtName = handler.event.raw || handler.event.parts?.map(p => p.value).join('');
|
|
661
|
+
this.emit(`// Bot event: ${evtName}`);
|
|
662
|
+
} else if (handler.type === 'BotSlashCmd') {
|
|
663
|
+
const cmdName = handler.name.raw || handler.name.parts?.map(p => p.value).join('');
|
|
664
|
+
this.emit(`// Bot slash command: /${cmdName}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
this.emitRaw('');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
rawBotType(bt) {
|
|
671
|
+
if (!bt) return 'discord';
|
|
672
|
+
return bt.raw || bt.parts?.map(p => p.value).join('') || 'discord';
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ===== Model (Class) =====
|
|
676
|
+
|
|
677
|
+
visitModel(node) {
|
|
678
|
+
this.models.set(node.name, node);
|
|
679
|
+
|
|
680
|
+
const ext = node.parent ? ` extends ${node.parent}` : '';
|
|
681
|
+
this.emit(`public static class ${node.name}${ext} {`);
|
|
682
|
+
this.indent++;
|
|
683
|
+
|
|
684
|
+
// Fields
|
|
685
|
+
for (const f of node.fields) {
|
|
686
|
+
const javaType = f.naideType ? this.mapType(f.naideType) : 'Object';
|
|
687
|
+
if (f.defaultValue) {
|
|
688
|
+
this.emit(`private ${javaType} ${f.name} = ${this.expr(f.defaultValue)};`);
|
|
689
|
+
} else {
|
|
690
|
+
this.emit(`private ${javaType} ${f.name};`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (node.fields.length > 0) this.emitRaw('');
|
|
695
|
+
|
|
696
|
+
// Constructor
|
|
697
|
+
if (node.fields.length > 0 || node.parent) {
|
|
698
|
+
const parentModel = node.parent ? this.models.get(node.parent) : null;
|
|
699
|
+
const parentFields = parentModel ? parentModel.fields : [];
|
|
700
|
+
const allFields = [...parentFields, ...node.fields];
|
|
701
|
+
|
|
702
|
+
const constructorParams = allFields.map(f => {
|
|
703
|
+
const javaType = f.naideType ? this.mapType(f.naideType) : 'Object';
|
|
704
|
+
return `${javaType} ${f.name}`;
|
|
705
|
+
}).join(', ');
|
|
706
|
+
|
|
707
|
+
this.emit(`public ${node.name}(${constructorParams}) {`);
|
|
708
|
+
this.indent++;
|
|
709
|
+
if (node.parent) {
|
|
710
|
+
const superArgs = parentFields.map(f => f.name).join(', ');
|
|
711
|
+
this.emit(`super(${superArgs});`);
|
|
712
|
+
}
|
|
713
|
+
for (const f of node.fields) {
|
|
714
|
+
this.emit(`this.${f.name} = ${f.name};`);
|
|
715
|
+
}
|
|
716
|
+
this.indent--;
|
|
717
|
+
this.emit('}');
|
|
718
|
+
this.emitRaw('');
|
|
719
|
+
|
|
720
|
+
// Getters and setters
|
|
721
|
+
for (const f of node.fields) {
|
|
722
|
+
const javaType = f.naideType ? this.mapType(f.naideType) : 'Object';
|
|
723
|
+
const capName = f.name.charAt(0).toUpperCase() + f.name.slice(1);
|
|
724
|
+
this.emit(`public ${javaType} get${capName}() { return this.${f.name}; }`);
|
|
725
|
+
this.emit(`public void set${capName}(${javaType} ${f.name}) { this.${f.name} = ${f.name}; }`);
|
|
726
|
+
this.emitRaw('');
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Methods
|
|
731
|
+
for (const method of node.methods) {
|
|
732
|
+
const returnType = method.returnType ? this.mapType(method.returnType) : 'Object';
|
|
733
|
+
const params = method.params.map(p => {
|
|
734
|
+
if (p.spread) return `Object... ${p.name}`;
|
|
735
|
+
return `Object ${p.name}`;
|
|
736
|
+
}).join(', ');
|
|
737
|
+
|
|
738
|
+
this.emit(`public ${returnType} ${method.name}(${params}) {`);
|
|
739
|
+
this.indent++;
|
|
740
|
+
if (method.body.length === 0) {
|
|
741
|
+
if (returnType !== 'void') this.emit('return null;');
|
|
742
|
+
} else {
|
|
743
|
+
for (const stmt of method.body) {
|
|
744
|
+
this.visitStatement(stmt);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
this.indent--;
|
|
748
|
+
this.emit('}');
|
|
749
|
+
this.emitRaw('');
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
this.indent--;
|
|
753
|
+
this.emit('}');
|
|
754
|
+
this.emitRaw('');
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ===== Events =====
|
|
758
|
+
|
|
759
|
+
visitOn(node) {
|
|
760
|
+
const event = this.expr(node.event);
|
|
761
|
+
this.emit(`// on ${event}`);
|
|
762
|
+
this.emit(`/* Event handler: */ {`);
|
|
763
|
+
this.indent++;
|
|
764
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
765
|
+
this.indent--;
|
|
766
|
+
this.emit('}');
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ===== Logging =====
|
|
770
|
+
|
|
771
|
+
visitLog(node) {
|
|
772
|
+
const args = node.args.map(a => this.expr(a));
|
|
773
|
+
if (args.length === 1) {
|
|
774
|
+
this.emit(`System.out.println(${args[0]});`);
|
|
775
|
+
} else {
|
|
776
|
+
this.emit(`System.out.println(${args.join(' + " " + ')});`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
visitThrow(node) {
|
|
781
|
+
const value = this.expr(node.value);
|
|
782
|
+
if (node.value.type === 'String') {
|
|
783
|
+
this.emit(`throw new RuntimeException(${value});`);
|
|
784
|
+
} else {
|
|
785
|
+
this.emit(`throw (RuntimeException) ${value};`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ===== Database =====
|
|
790
|
+
|
|
791
|
+
visitDbConnect(node) {
|
|
792
|
+
this.addImport('java.sql.Connection');
|
|
793
|
+
this.addImport('java.sql.DriverManager');
|
|
794
|
+
this.emit(`Connection db = DriverManager.getConnection(${this.expr(node.connectionString)});`);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
visitDbDir(node) {
|
|
798
|
+
const raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'data/';
|
|
799
|
+
this.emit(`// db dir: ${raw}`);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
visitDbSql(node) {
|
|
803
|
+
this.addImport('java.sql.Connection');
|
|
804
|
+
this.addImport('java.sql.DriverManager');
|
|
805
|
+
this.addImport('java.sql.Statement');
|
|
806
|
+
this.addImport('java.sql.ResultSet');
|
|
807
|
+
const driverRaw = node.driver.raw || node.driver.parts?.map(p => p.value).join('') || 'sqlite';
|
|
808
|
+
|
|
809
|
+
if (driverRaw === 'sqlite') {
|
|
810
|
+
const conn = node.connection ? this.stringValue(node.connection) : '"jdbc:sqlite:data.db"';
|
|
811
|
+
this.emit(`Connection __db = DriverManager.getConnection(${conn});`);
|
|
812
|
+
this.emit(`__db.createStatement().execute("PRAGMA journal_mode=WAL");`);
|
|
813
|
+
} else if (driverRaw === 'postgres' || driverRaw === 'pg') {
|
|
814
|
+
const conn = node.connection ? this.stringValue(node.connection) : 'System.getenv("DATABASE_URL")';
|
|
815
|
+
this.emit(`Connection __db = DriverManager.getConnection(${conn});`);
|
|
816
|
+
}
|
|
817
|
+
this.emitRaw('');
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
visitAwaitAllStatement(node) {
|
|
821
|
+
this.addImport('java.util.concurrent.CompletableFuture');
|
|
822
|
+
const exprs = node.expressions.map(e => this.expr(e)).join(', ');
|
|
823
|
+
this.emit(`CompletableFuture.allOf(${exprs}).join();`);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ===== Schema =====
|
|
827
|
+
|
|
828
|
+
visitSchema(node) {
|
|
829
|
+
this.emit(`// Schema: ${node.name}`);
|
|
830
|
+
this.emit(`public static class ${node.name} {`);
|
|
831
|
+
this.indent++;
|
|
832
|
+
for (const field of node.fields) {
|
|
833
|
+
const javaType = this.mapType(field.type);
|
|
834
|
+
this.emit(`private ${javaType} ${field.name};`);
|
|
835
|
+
}
|
|
836
|
+
if (node.fields.length > 0) {
|
|
837
|
+
this.emitRaw('');
|
|
838
|
+
// Constructor
|
|
839
|
+
const params = node.fields.map(f => `${this.mapType(f.type)} ${f.name}`).join(', ');
|
|
840
|
+
this.emit(`public ${node.name}(${params}) {`);
|
|
841
|
+
this.indent++;
|
|
842
|
+
for (const f of node.fields) {
|
|
843
|
+
this.emit(`this.${f.name} = ${f.name};`);
|
|
844
|
+
}
|
|
845
|
+
this.indent--;
|
|
846
|
+
this.emit('}');
|
|
847
|
+
this.emitRaw('');
|
|
848
|
+
// Getters
|
|
849
|
+
for (const f of node.fields) {
|
|
850
|
+
const cap = f.name.charAt(0).toUpperCase() + f.name.slice(1);
|
|
851
|
+
this.emit(`public ${this.mapType(f.type)} get${cap}() { return this.${f.name}; }`);
|
|
852
|
+
this.emit(`public void set${cap}(${this.mapType(f.type)} ${f.name}) { this.${f.name} = ${f.name}; }`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
this.indent--;
|
|
856
|
+
this.emit('}');
|
|
857
|
+
this.emitRaw('');
|
|
858
|
+
this.schemas.set(node.name, node);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// ===== High-level features (stubs for Java) =====
|
|
862
|
+
|
|
863
|
+
visitCrud(node) {
|
|
864
|
+
const path = this.rawString(node.path);
|
|
865
|
+
this.emit(`// TODO: CRUD for ${node.schemaName} at ${path} - use Spring Data or manual routes`);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
visitAuth(node) {
|
|
869
|
+
const secret = this.expr(node.secret);
|
|
870
|
+
this.authSecret = secret;
|
|
871
|
+
this.emit(`// TODO: Auth middleware - use JWT library (io.jsonwebtoken)`);
|
|
872
|
+
this.emit(`String __authSecret = ${secret};`);
|
|
873
|
+
this.emitRaw('');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
visitCors(node) {
|
|
877
|
+
const origins = this.expr(node.origins);
|
|
878
|
+
this.emit(`// TODO: CORS - set Access-Control-Allow-Origin: ${origins}`);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
visitLimit(node) {
|
|
882
|
+
const path = this.rawString(node.path);
|
|
883
|
+
const max = this.expr(node.max);
|
|
884
|
+
const window = this.expr(node.window);
|
|
885
|
+
this.emit(`// TODO: Rate limit ${path}: ${max} requests per ${window}`);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
visitStatic(node) {
|
|
889
|
+
let raw = node.path.raw || node.path.parts?.map(p => p.value).join('') || 'public';
|
|
890
|
+
this.emit(`// TODO: Serve static files from ${raw}`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
visitWs(node) {
|
|
894
|
+
this.emit(`// TODO: WebSocket - use Java-WebSocket library`);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
visitGroup(node) {
|
|
898
|
+
const prefix = this.rawString(node.prefix);
|
|
899
|
+
this.emit(`// TODO: Route group ${prefix}`);
|
|
900
|
+
for (const child of node.routes) {
|
|
901
|
+
this.visitStatement(child);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
visitErrorHandler(node) {
|
|
906
|
+
this.emit(`// Error handler`);
|
|
907
|
+
this.emit(`/* error handler */ {`);
|
|
908
|
+
this.indent++;
|
|
909
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
910
|
+
this.indent--;
|
|
911
|
+
this.emit('}');
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
visitCookie(node) {
|
|
915
|
+
this.emit('// TODO: Cookie support - use HttpCookie');
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
visitUpload(node) {
|
|
919
|
+
const path = this.rawString(node.path);
|
|
920
|
+
this.emit(`// TODO: File upload at ${path} - use Apache Commons FileUpload`);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
visitSession(node) {
|
|
924
|
+
this.emit('// TODO: Session support - use HttpSession or custom implementation');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
visitView(node) {
|
|
928
|
+
const dir = this.rawString(node.dir);
|
|
929
|
+
this.emit(`// TODO: View engine for templates in ${dir} - use Thymeleaf or Freemarker`);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
visitSse(node) {
|
|
933
|
+
const path = this.rawString(node.path);
|
|
934
|
+
this.emit(`// TODO: Server-Sent Events at ${path}`);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
visitCache(node) {
|
|
938
|
+
const path = this.rawString(node.path);
|
|
939
|
+
const duration = this.expr(node.duration);
|
|
940
|
+
this.emit(`// TODO: Cache ${path} for ${duration}`);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
visitMiddlewareRef(node) {
|
|
944
|
+
this.emit(`// Middleware: ${node.name}`);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
visitReturnRender(node) {
|
|
948
|
+
const template = this.expr(node.template);
|
|
949
|
+
this.emit(`// TODO: Render template ${template} - use Thymeleaf`);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
visitReturnRedirect(node) {
|
|
953
|
+
this.emit(`exchange.getResponseHeaders().set("Location", ${this.expr(node.url)});`);
|
|
954
|
+
this.emit(`exchange.sendResponseHeaders(${this.expr(node.statusCode)}, -1);`);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
visitReturnDownload(node) {
|
|
958
|
+
this.addImport('java.nio.file.Files');
|
|
959
|
+
this.addImport('java.nio.file.Path');
|
|
960
|
+
this.emit(`exchange.getResponseHeaders().set("Content-Disposition", "attachment; filename=\\"" + ${this.expr(node.filename)} + "\\"");`);
|
|
961
|
+
this.emit(`byte[] __dlBytes = Files.readAllBytes(Path.of(${this.expr(node.filePath)}));`);
|
|
962
|
+
this.emit(`exchange.sendResponseHeaders(200, __dlBytes.length);`);
|
|
963
|
+
this.emit(`try (OutputStream os = exchange.getResponseBody()) { os.write(__dlBytes); }`);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
visitValidate(node) {
|
|
967
|
+
const path = this.rawString(node.path);
|
|
968
|
+
this.emit(`// TODO: Validate ${path} against ${node.schemaName}Schema`);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// ===== Tests =====
|
|
972
|
+
|
|
973
|
+
visitTest(node) {
|
|
974
|
+
const name = this.rawString(node.name);
|
|
975
|
+
const funcName = 'test_' + name.replace(/[^a-zA-Z0-9]/g, '_').replace(/_+/g, '_');
|
|
976
|
+
|
|
977
|
+
this.emit(`// @Test`);
|
|
978
|
+
this.emit(`public static void ${funcName}() {`);
|
|
979
|
+
this.indent++;
|
|
980
|
+
if (node.body.length === 0) {
|
|
981
|
+
this.emit('// empty test');
|
|
982
|
+
} else {
|
|
983
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
984
|
+
}
|
|
985
|
+
this.indent--;
|
|
986
|
+
this.emit('}');
|
|
987
|
+
this.emitRaw('');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
visitAssert(node) {
|
|
991
|
+
const exprNode = node.expr;
|
|
992
|
+
if (exprNode.type === 'Binary' && (exprNode.op === '===' || exprNode.op === '==')) {
|
|
993
|
+
this.emit(`assert ${this.expr(exprNode.left)}.equals(${this.expr(exprNode.right)}) : "Assertion failed";`);
|
|
994
|
+
} else if (exprNode.type === 'Binary' && (exprNode.op === '!==' || exprNode.op === '!=')) {
|
|
995
|
+
this.emit(`assert !${this.expr(exprNode.left)}.equals(${this.expr(exprNode.right)}) : "Assertion failed";`);
|
|
996
|
+
} else {
|
|
997
|
+
this.emit(`assert (boolean) (${this.expr(exprNode)}) : "Assertion failed";`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// ===== Env =====
|
|
1002
|
+
|
|
1003
|
+
visitEnv(node) {
|
|
1004
|
+
this.emitRaw('');
|
|
1005
|
+
for (const v of node.vars) {
|
|
1006
|
+
let defaultVal = null;
|
|
1007
|
+
for (const mod of v.modifiers) {
|
|
1008
|
+
if (mod.name === 'default') {
|
|
1009
|
+
defaultVal = this.expr(mod.args[0]);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (defaultVal !== null) {
|
|
1014
|
+
this.emit(`String __env_${v.name} = System.getenv("${v.name}");`);
|
|
1015
|
+
this.emit(`var ${v.name} = __env_${v.name} != null ? __env_${v.name} : ${defaultVal};`);
|
|
1016
|
+
} else {
|
|
1017
|
+
this.emit(`var ${v.name} = System.getenv("${v.name}");`);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Type coercion
|
|
1021
|
+
if (v.type === 'int') {
|
|
1022
|
+
this.emit(`int ${v.name}Val = ${v.name} != null ? Integer.parseInt(${v.name}.toString()) : 0;`);
|
|
1023
|
+
} else if (v.type === 'num') {
|
|
1024
|
+
this.emit(`double ${v.name}Val = ${v.name} != null ? Double.parseDouble(${v.name}.toString()) : 0.0;`);
|
|
1025
|
+
} else if (v.type === 'bool') {
|
|
1026
|
+
this.emit(`boolean ${v.name}Val = "true".equalsIgnoreCase(String.valueOf(${v.name}));`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
this.emitRaw('');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// ===== Every (scheduled) =====
|
|
1033
|
+
|
|
1034
|
+
visitEvery(node) {
|
|
1035
|
+
this.addImport('java.util.Timer');
|
|
1036
|
+
this.addImport('java.util.TimerTask');
|
|
1037
|
+
const interval = this.expr(node.interval);
|
|
1038
|
+
|
|
1039
|
+
this.emit(`new Timer().scheduleAtFixedRate(new TimerTask() {`);
|
|
1040
|
+
this.indent++;
|
|
1041
|
+
this.emit(`@Override`);
|
|
1042
|
+
this.emit(`public void run() {`);
|
|
1043
|
+
this.indent++;
|
|
1044
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
1045
|
+
this.indent--;
|
|
1046
|
+
this.emit('}');
|
|
1047
|
+
this.indent--;
|
|
1048
|
+
this.emit(`}, 0, ${interval}); // interval in ms`);
|
|
1049
|
+
this.emitRaw('');
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
visitWatch(node) {
|
|
1053
|
+
const params = node.params.length > 0 ? node.params.join(', ') : 'event';
|
|
1054
|
+
this.emit(`// watch "${node.eventName}"`);
|
|
1055
|
+
this.emit(`/* event watcher */ {`);
|
|
1056
|
+
this.indent++;
|
|
1057
|
+
for (const stmt of node.body) this.visitStatement(stmt);
|
|
1058
|
+
this.indent--;
|
|
1059
|
+
this.emit('}');
|
|
1060
|
+
this.emitRaw('');
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ===== Queue =====
|
|
1064
|
+
|
|
1065
|
+
visitQueue(node) {
|
|
1066
|
+
this.addImport('java.util.concurrent.ExecutorService');
|
|
1067
|
+
this.addImport('java.util.concurrent.Executors');
|
|
1068
|
+
this.emit(`ExecutorService ${node.name} = Executors.newFixedThreadPool(4);`);
|
|
1069
|
+
for (const job of node.jobs) {
|
|
1070
|
+
const jobName = this.generateString(job.name);
|
|
1071
|
+
this.emit(`// Job: ${jobName}`);
|
|
1072
|
+
}
|
|
1073
|
+
this.emitRaw('');
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// ===== Prompt =====
|
|
1077
|
+
|
|
1078
|
+
visitPrompt(node) {
|
|
1079
|
+
const lines = node.lines.map(l => this.generateString(l));
|
|
1080
|
+
const template = lines.length === 1 ? lines[0] : lines.join(' + "\\n" + ');
|
|
1081
|
+
this.emit(`// Prompt template: ${node.name}`);
|
|
1082
|
+
this.emit(`public static String ${node.name}(Map<String, String> vars) {`);
|
|
1083
|
+
this.indent++;
|
|
1084
|
+
this.addImport('java.util.Map');
|
|
1085
|
+
this.emit(`String template = ${template};`);
|
|
1086
|
+
this.emit(`for (var entry : vars.entrySet()) {`);
|
|
1087
|
+
this.indent++;
|
|
1088
|
+
this.emit(`template = template.replace("{" + entry.getKey() + "}", entry.getValue());`);
|
|
1089
|
+
this.indent--;
|
|
1090
|
+
this.emit('}');
|
|
1091
|
+
this.emit('return template;');
|
|
1092
|
+
this.indent--;
|
|
1093
|
+
this.emit('}');
|
|
1094
|
+
this.emitRaw('');
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// ===== OpenAPI =====
|
|
1098
|
+
|
|
1099
|
+
visitOpenapi(node) {
|
|
1100
|
+
this.emit('// TODO: OpenAPI spec generation - use springdoc-openapi');
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// ===== Page =====
|
|
1104
|
+
|
|
1105
|
+
visitPage(node) {
|
|
1106
|
+
const filename = this.rawString(node.filename);
|
|
1107
|
+
this.addImport('java.io.FileWriter');
|
|
1108
|
+
this.addImport('java.io.IOException');
|
|
1109
|
+
|
|
1110
|
+
const headParts = [];
|
|
1111
|
+
const bodyParts = [];
|
|
1112
|
+
let pageTitle = 'NAIDE Page';
|
|
1113
|
+
|
|
1114
|
+
for (const el of node.elements) {
|
|
1115
|
+
this.classifyPageElement(el, headParts, bodyParts, (t) => { pageTitle = t; });
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
this.emit(`try (FileWriter __fw = new FileWriter("${filename}")) {`);
|
|
1119
|
+
this.indent++;
|
|
1120
|
+
this.emit(`__fw.write("<!DOCTYPE html>\\n<html lang=\\"en\\">\\n<head>\\n");`);
|
|
1121
|
+
this.emit(`__fw.write("<meta charset=\\"UTF-8\\">\\n");`);
|
|
1122
|
+
this.emit(`__fw.write("<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\">\\n");`);
|
|
1123
|
+
this.emit(`__fw.write("<title>${pageTitle}</title>\\n");`);
|
|
1124
|
+
for (const h of headParts) {
|
|
1125
|
+
this.emit(`__fw.write("${this.escapeJavaString(h)}\\n");`);
|
|
1126
|
+
}
|
|
1127
|
+
this.emit(`__fw.write("</head>\\n<body>\\n");`);
|
|
1128
|
+
for (const b of bodyParts) {
|
|
1129
|
+
this.emit(`__fw.write("${this.escapeJavaString(b)}\\n");`);
|
|
1130
|
+
}
|
|
1131
|
+
this.emit(`__fw.write("</body>\\n</html>\\n");`);
|
|
1132
|
+
this.indent--;
|
|
1133
|
+
this.emit('} catch (IOException e) { e.printStackTrace(); }');
|
|
1134
|
+
this.emitRaw('');
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
classifyPageElement(el, head, body, setTitle) {
|
|
1138
|
+
const tag = el.tag;
|
|
1139
|
+
const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
|
|
1140
|
+
const arg1 = el.args[1] ? this.rawString(el.args[1]) : '';
|
|
1141
|
+
if (tag === 'title') { setTitle(arg0); return; }
|
|
1142
|
+
if (tag === 'style' || tag === 'link') { head.push(`<link rel="stylesheet" href="${arg0}">`); return; }
|
|
1143
|
+
if (tag === 'meta') { head.push(`<meta name="${arg0}" content="${arg1}">`); return; }
|
|
1144
|
+
if (tag === 'script') { body.push(`<script src="${arg0}"></script>`); return; }
|
|
1145
|
+
if (tag === 'img') { body.push(`<img src="${arg0}" alt="${arg1}">`); return; }
|
|
1146
|
+
if (tag === 'a') { body.push(`<a href="${arg0}">${arg1}</a>`); return; }
|
|
1147
|
+
if (tag === 'input') { body.push(`<input type="${arg0}" name="${arg1}">`); return; }
|
|
1148
|
+
if (el.children && el.children.length > 0) {
|
|
1149
|
+
const cls = arg0 ? ` class="${arg0}"` : '';
|
|
1150
|
+
body.push(`<${tag}${cls}>`);
|
|
1151
|
+
for (const child of el.children) this.classifyPageElement(child, head, body, setTitle);
|
|
1152
|
+
body.push(`</${tag}>`);
|
|
1153
|
+
} else {
|
|
1154
|
+
body.push(`<${tag}>${arg0}</${tag}>`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
escapeJavaString(s) {
|
|
1159
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// ===== CLI App =====
|
|
1163
|
+
|
|
1164
|
+
visitCli(node) {
|
|
1165
|
+
const desc = node.description ? this.rawString(node.description) : node.name;
|
|
1166
|
+
this.addImport('java.util.HashMap');
|
|
1167
|
+
|
|
1168
|
+
this.emit(`var __cliArgs = new HashMap<String, String>();`);
|
|
1169
|
+
this.emit(`// CLI: ${desc}`);
|
|
1170
|
+
this.emit(`for (int __i = 0; __i < args.length; __i++) {`);
|
|
1171
|
+
this.indent++;
|
|
1172
|
+
for (const arg of node.args) {
|
|
1173
|
+
const argName = this.rawString(arg.name);
|
|
1174
|
+
this.emit(`if ("--${argName}".equals(args[__i]) && __i + 1 < args.length) { __cliArgs.put("${argName}", args[++__i]); continue; }`);
|
|
1175
|
+
}
|
|
1176
|
+
for (const flag of node.flags) {
|
|
1177
|
+
const s = this.rawString(flag.short);
|
|
1178
|
+
const l = this.rawString(flag.long);
|
|
1179
|
+
this.emit(`if ("-${s}".equals(args[__i]) || "--${l}".equals(args[__i])) { __cliArgs.put("${l}", "true"); continue; }`);
|
|
1180
|
+
}
|
|
1181
|
+
this.indent--;
|
|
1182
|
+
this.emit('}');
|
|
1183
|
+
this.emitRaw('');
|
|
1184
|
+
|
|
1185
|
+
if (node.run) {
|
|
1186
|
+
for (const stmt of node.run.body) this.visitStatement(stmt);
|
|
1187
|
+
}
|
|
1188
|
+
this.emitRaw('');
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ===== Mail =====
|
|
1192
|
+
|
|
1193
|
+
visitMail(node) {
|
|
1194
|
+
this.addImport('java.util.Properties');
|
|
1195
|
+
this.addImport('javax.mail.*');
|
|
1196
|
+
this.addImport('javax.mail.internet.*');
|
|
1197
|
+
|
|
1198
|
+
const host = this.generateString(node.host);
|
|
1199
|
+
const port = this.expr(node.port);
|
|
1200
|
+
const user = node.user ? this.expr(node.user) : 'System.getenv("MAIL_USER")';
|
|
1201
|
+
const pass = node.pass ? this.expr(node.pass) : 'System.getenv("MAIL_PASS")';
|
|
1202
|
+
|
|
1203
|
+
this.emit(`// Mail configuration`);
|
|
1204
|
+
this.emit(`Properties __mailProps = new Properties();`);
|
|
1205
|
+
this.emit(`__mailProps.put("mail.smtp.host", ${host});`);
|
|
1206
|
+
this.emit(`__mailProps.put("mail.smtp.port", String.valueOf(${port}));`);
|
|
1207
|
+
this.emit(`__mailProps.put("mail.smtp.auth", "true");`);
|
|
1208
|
+
this.emit(`__mailProps.put("mail.smtp.starttls.enable", "true");`);
|
|
1209
|
+
this.emitRaw('');
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// ===== Desktop =====
|
|
1213
|
+
|
|
1214
|
+
visitDesktop(node) {
|
|
1215
|
+
const title = node.title ? this.rawString(node.title) : node.name;
|
|
1216
|
+
const width = node.width ? this.expr(node.width) : '800';
|
|
1217
|
+
const height = node.height ? this.expr(node.height) : '600';
|
|
1218
|
+
|
|
1219
|
+
this.addImport('javax.swing.*');
|
|
1220
|
+
|
|
1221
|
+
this.emit(`JFrame ${node.name} = new JFrame("${title}");`);
|
|
1222
|
+
this.emit(`${node.name}.setSize(${width}, ${height});`);
|
|
1223
|
+
this.emit(`${node.name}.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);`);
|
|
1224
|
+
this.emit(`${node.name}.setVisible(true);`);
|
|
1225
|
+
this.emitRaw('');
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// ===== Screen =====
|
|
1229
|
+
|
|
1230
|
+
visitScreen(node) {
|
|
1231
|
+
this.addImport('javax.swing.*');
|
|
1232
|
+
this.addImport('java.awt.*');
|
|
1233
|
+
|
|
1234
|
+
this.emit(`// Screen: ${node.name} (Swing)`);
|
|
1235
|
+
this.emit(`JPanel ${node.name}Panel = new JPanel();`);
|
|
1236
|
+
this.emit(`${node.name}Panel.setLayout(new BoxLayout(${node.name}Panel, BoxLayout.Y_AXIS));`);
|
|
1237
|
+
|
|
1238
|
+
for (const el of node.elements) {
|
|
1239
|
+
const tag = el.tag;
|
|
1240
|
+
const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
|
|
1241
|
+
if (tag === 'text') {
|
|
1242
|
+
this.emit(`${node.name}Panel.add(new JLabel("${arg0}"));`);
|
|
1243
|
+
} else if (tag === 'button') {
|
|
1244
|
+
this.emit(`${node.name}Panel.add(new JButton("${arg0}"));`);
|
|
1245
|
+
} else if (tag === 'input') {
|
|
1246
|
+
this.emit(`${node.name}Panel.add(new JTextField("${arg0}", 20));`);
|
|
1247
|
+
} else {
|
|
1248
|
+
this.emit(`${node.name}Panel.add(new JLabel("${arg0}"));`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
this.emitRaw('');
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// ===== Domain-specific stubs =====
|
|
1255
|
+
|
|
1256
|
+
visitOauth(node) {
|
|
1257
|
+
const provider = this.rawString(node.provider);
|
|
1258
|
+
const clientId = this.expr(node.clientId);
|
|
1259
|
+
const clientSecret = this.expr(node.clientSecret);
|
|
1260
|
+
this.emit(`// TODO: OAuth (${provider}) - use Spring Security OAuth2 or ScribeJava`);
|
|
1261
|
+
this.emit(`// Client ID: ${clientId}`);
|
|
1262
|
+
this.emit(`// Client Secret: ${clientSecret}`);
|
|
1263
|
+
this.emitRaw('');
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
visitPay(node) {
|
|
1267
|
+
const provider = this.rawString(node.provider);
|
|
1268
|
+
const secretKey = this.expr(node.secretKey);
|
|
1269
|
+
this.emit(`// TODO: Payment (${provider}) - use Stripe Java SDK (com.stripe:stripe-java)`);
|
|
1270
|
+
this.emit(`// Secret Key: ${secretKey}`);
|
|
1271
|
+
this.emitRaw('');
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
visitStorage(node) {
|
|
1275
|
+
const provider = this.rawString(node.provider);
|
|
1276
|
+
const bucket = this.expr(node.bucket);
|
|
1277
|
+
if (provider === 's3') {
|
|
1278
|
+
this.emit(`// TODO: S3 Storage - use AWS SDK for Java (software.amazon.awssdk:s3)`);
|
|
1279
|
+
} else if (provider === 'gcs') {
|
|
1280
|
+
this.emit(`// TODO: GCS Storage - use Google Cloud Storage SDK`);
|
|
1281
|
+
} else {
|
|
1282
|
+
this.emit(`// TODO: ${provider} Storage`);
|
|
1283
|
+
}
|
|
1284
|
+
this.emit(`// Bucket: ${bucket}`);
|
|
1285
|
+
this.emitRaw('');
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
visitPdf(node) {
|
|
1289
|
+
const filename = this.rawString(node.filename);
|
|
1290
|
+
this.emit(`// TODO: PDF generation (${filename}) - use Apache PDFBox or iText`);
|
|
1291
|
+
for (const el of node.elements) {
|
|
1292
|
+
const tag = el.tag;
|
|
1293
|
+
const arg0 = el.args[0] ? this.rawString(el.args[0]) : '';
|
|
1294
|
+
this.emit(`// PDF element: ${tag} "${arg0}"`);
|
|
1295
|
+
}
|
|
1296
|
+
this.emitRaw('');
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
visitI18n(node) {
|
|
1300
|
+
const dir = this.rawString(node.dir);
|
|
1301
|
+
const defaultLang = node.defaultLang ? this.rawString(node.defaultLang) : 'en';
|
|
1302
|
+
this.addImport('java.util.Locale');
|
|
1303
|
+
this.addImport('java.util.ResourceBundle');
|
|
1304
|
+
this.emit(`// i18n: default=${defaultLang}, dir=${dir}`);
|
|
1305
|
+
this.emit(`Locale __locale = Locale.forLanguageTag("${defaultLang}");`);
|
|
1306
|
+
this.emit(`// ResourceBundle __bundle = ResourceBundle.getBundle("messages", __locale);`);
|
|
1307
|
+
this.emitRaw('');
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
visitPush(node) {
|
|
1311
|
+
this.emit('// TODO: Push notifications - use Firebase Admin SDK for Java');
|
|
1312
|
+
this.emitRaw('');
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
visitSearch(node) {
|
|
1316
|
+
const engine = this.rawString(node.engine);
|
|
1317
|
+
this.emit(`// TODO: Search (${engine}) - use ${engine === 'meilisearch' ? 'Meilisearch Java SDK' : 'Elasticsearch Java client'}`);
|
|
1318
|
+
this.emitRaw('');
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
visitImage(node) {
|
|
1322
|
+
const input = this.expr(node.input);
|
|
1323
|
+
const output = node.output ? this.expr(node.output) : input;
|
|
1324
|
+
this.addImport('javax.imageio.ImageIO');
|
|
1325
|
+
this.addImport('java.awt.image.BufferedImage');
|
|
1326
|
+
this.addImport('java.io.File');
|
|
1327
|
+
|
|
1328
|
+
this.emit(`BufferedImage __img = ImageIO.read(new File(${input}));`);
|
|
1329
|
+
for (const op of node.operations) {
|
|
1330
|
+
if (op.op === 'resize') {
|
|
1331
|
+
const w = op.args[0] || 800;
|
|
1332
|
+
const h = op.args[1] || 600;
|
|
1333
|
+
this.emit(`BufferedImage __resized = new BufferedImage(${w}, ${h}, __img.getType());`);
|
|
1334
|
+
this.emit(`__resized.getGraphics().drawImage(__img, 0, 0, ${w}, ${h}, null);`);
|
|
1335
|
+
this.emit(`__img = __resized;`);
|
|
1336
|
+
} else if (op.op === 'grayscale' || op.op === 'greyscale') {
|
|
1337
|
+
this.addImport('java.awt.color.ColorSpace');
|
|
1338
|
+
this.addImport('java.awt.image.ColorConvertOp');
|
|
1339
|
+
this.emit(`__img = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(__img, null);`);
|
|
1340
|
+
} else {
|
|
1341
|
+
this.emit(`// TODO: Image op "${op.op}" - use Java2D or ImageIO`);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
this.emit(`ImageIO.write(__img, "png", new File(${output}));`);
|
|
1345
|
+
this.emit(`System.out.println("Processed: " + ${output});`);
|
|
1346
|
+
this.emitRaw('');
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
visitCsv(node) {
|
|
1350
|
+
const name = this.rawString(node.name);
|
|
1351
|
+
const format = node.format ? this.rawString(node.format) : 'csv';
|
|
1352
|
+
const output = node.output ? this.rawString(node.output) : `${name}.${format}`;
|
|
1353
|
+
this.addImport('java.io.FileWriter');
|
|
1354
|
+
|
|
1355
|
+
if (format === 'xlsx' || format === 'excel') {
|
|
1356
|
+
this.emit(`// TODO: Excel export (${output}) - use Apache POI`);
|
|
1357
|
+
} else {
|
|
1358
|
+
this.emit(`// CSV export: ${output}`);
|
|
1359
|
+
this.emit(`try (FileWriter __csvWriter = new FileWriter("${output}")) {`);
|
|
1360
|
+
this.indent++;
|
|
1361
|
+
const columns = node.columns.map(c => this.rawString(c));
|
|
1362
|
+
if (columns.length > 0) {
|
|
1363
|
+
this.emit(`__csvWriter.write("${columns.join(',')}" + "\\n");`);
|
|
1364
|
+
}
|
|
1365
|
+
this.emit(`// TODO: Write data rows`);
|
|
1366
|
+
this.indent--;
|
|
1367
|
+
this.emit('} catch (IOException e) { e.printStackTrace(); }');
|
|
1368
|
+
}
|
|
1369
|
+
this.emit(`System.out.println("Exported: ${output}");`);
|
|
1370
|
+
this.emitRaw('');
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
visitLogging(node) {
|
|
1374
|
+
this.addImport('java.util.logging.Logger');
|
|
1375
|
+
this.addImport('java.util.logging.Level');
|
|
1376
|
+
const name = this.rawString(node.name);
|
|
1377
|
+
const level = node.level ? this.rawString(node.level).toUpperCase() : 'INFO';
|
|
1378
|
+
this.emit(`Logger logger = Logger.getLogger("${name}");`);
|
|
1379
|
+
this.emit(`logger.setLevel(Level.${level});`);
|
|
1380
|
+
this.emitRaw('');
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
visitMigrate(node) {
|
|
1384
|
+
const name = this.rawString(node.name);
|
|
1385
|
+
this.emit(`// Migration: ${name}`);
|
|
1386
|
+
this.emit(`/* migrate up */ {`);
|
|
1387
|
+
this.indent++;
|
|
1388
|
+
for (const stmt of node.up) this.visitStatement(stmt);
|
|
1389
|
+
this.indent--;
|
|
1390
|
+
this.emit('}');
|
|
1391
|
+
this.emit(`/* migrate down */ {`);
|
|
1392
|
+
this.indent++;
|
|
1393
|
+
for (const stmt of node.down) this.visitStatement(stmt);
|
|
1394
|
+
this.indent--;
|
|
1395
|
+
this.emit('}');
|
|
1396
|
+
this.emitRaw('');
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
visitGrpc(node) {
|
|
1400
|
+
const name = this.rawString(node.name);
|
|
1401
|
+
const port = node.port ? this.expr(node.port) : '50051';
|
|
1402
|
+
this.emit(`// TODO: gRPC server "${name}" on port ${port} - use io.grpc:grpc-java`);
|
|
1403
|
+
for (const rpc of node.rpcs) {
|
|
1404
|
+
this.emit(`// RPC: ${rpc.method}`);
|
|
1405
|
+
}
|
|
1406
|
+
this.emitRaw('');
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
visitWebrtc(node) {
|
|
1410
|
+
const name = this.rawString(node.name);
|
|
1411
|
+
this.emit(`// TODO: WebRTC signaling "${name}" - use Java-WebSocket for signaling server`);
|
|
1412
|
+
this.emitRaw('');
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
visitBlockchain(node) {
|
|
1416
|
+
const name = this.rawString(node.name);
|
|
1417
|
+
const provider = node.provider ? this.expr(node.provider) : '"http://localhost:8545"';
|
|
1418
|
+
this.emit(`// TODO: Blockchain "${name}" - use web3j (org.web3j:core)`);
|
|
1419
|
+
this.emit(`// Provider: ${provider}`);
|
|
1420
|
+
this.emitRaw('');
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// ===== Expression Generation =====
|
|
1424
|
+
|
|
1425
|
+
expr(node) {
|
|
1426
|
+
if (!node) return 'null';
|
|
1427
|
+
|
|
1428
|
+
switch (node.type) {
|
|
1429
|
+
case 'Number': return node.value;
|
|
1430
|
+
case 'String': return this.generateString(node.value);
|
|
1431
|
+
case 'Bool': return node.value ? 'true' : 'false';
|
|
1432
|
+
case 'Null': return 'null';
|
|
1433
|
+
case 'Self': return 'this';
|
|
1434
|
+
case 'Identifier': return this.mapIdentifier(node.name);
|
|
1435
|
+
|
|
1436
|
+
case 'Binary': {
|
|
1437
|
+
const op = this.mapBinaryOp(node.op);
|
|
1438
|
+
const l = this.expr(node.left);
|
|
1439
|
+
const r = this.expr(node.right);
|
|
1440
|
+
const simple = node.left.type !== 'Binary' && node.right.type !== 'Binary';
|
|
1441
|
+
return simple ? `${l} ${op} ${r}` : `(${l} ${op} ${r})`;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
case 'Unary': {
|
|
1445
|
+
const op = node.op === 'not' ? '!' : node.op;
|
|
1446
|
+
return `${op}${this.expr(node.expr)}`;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
case 'Await':
|
|
1450
|
+
// Java doesn't have await syntax; emit as-is for CompletableFuture
|
|
1451
|
+
return `${this.expr(node.expr)}`;
|
|
1452
|
+
|
|
1453
|
+
case 'AwaitAllExpr': {
|
|
1454
|
+
this.addImport('java.util.concurrent.CompletableFuture');
|
|
1455
|
+
return `CompletableFuture.allOf(${this.expr(node.expr)}).join()`;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
case 'Spread':
|
|
1459
|
+
// Java doesn't have spread; just use the array
|
|
1460
|
+
return this.expr(node.expr);
|
|
1461
|
+
|
|
1462
|
+
case 'New':
|
|
1463
|
+
return `new ${this.expr(node.expr)}`;
|
|
1464
|
+
|
|
1465
|
+
case 'TypeOf':
|
|
1466
|
+
return `${this.expr(node.expr)}.getClass().getSimpleName()`;
|
|
1467
|
+
|
|
1468
|
+
case 'MemberAccess':
|
|
1469
|
+
return this.mapMemberAccess(node);
|
|
1470
|
+
|
|
1471
|
+
case 'OptionalAccess':
|
|
1472
|
+
// Java doesn't have ?. — emit a null-safe pattern
|
|
1473
|
+
return `(${this.expr(node.object)} != null ? ${this.expr(node.object)}.${node.property} : null)`;
|
|
1474
|
+
|
|
1475
|
+
case 'Call':
|
|
1476
|
+
return this.generateCall(node);
|
|
1477
|
+
|
|
1478
|
+
case 'IndexAccess':
|
|
1479
|
+
return `${this.expr(node.object)}[${this.expr(node.index)}]`;
|
|
1480
|
+
|
|
1481
|
+
case 'Array': {
|
|
1482
|
+
this.addImport('java.util.List');
|
|
1483
|
+
if (node.elements.length === 0) return 'new java.util.ArrayList<>()';
|
|
1484
|
+
return `java.util.List.of(${node.elements.map(e => this.expr(e)).join(', ')})`;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
case 'Object': {
|
|
1488
|
+
this.addImport('java.util.Map');
|
|
1489
|
+
if (node.properties.length === 0) return 'new java.util.HashMap<>()';
|
|
1490
|
+
const entries = node.properties.map(p => {
|
|
1491
|
+
if (p.type === 'spread') return `/* spread: ${this.expr(p.value)} */`;
|
|
1492
|
+
if (p.type === 'shorthand') return `"${p.key}", ${p.key}`;
|
|
1493
|
+
const key = typeof p.key === 'string' ? `"${p.key}"` :
|
|
1494
|
+
(p.key.type === 'String' ? this.generateString(p.key.value) : `"${this.expr(p.key)}"`);
|
|
1495
|
+
return `${key}, ${this.expr(p.value)}`;
|
|
1496
|
+
});
|
|
1497
|
+
if (entries.length <= 10) {
|
|
1498
|
+
return `Map.of(${entries.join(', ')})`;
|
|
1499
|
+
}
|
|
1500
|
+
return `Map.of(${entries.slice(0, 10).join(', ')})`;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
case 'ArrowFn': {
|
|
1504
|
+
const params = node.params.map(p => p.name).join(', ');
|
|
1505
|
+
const body = this.expr(node.body);
|
|
1506
|
+
if (node.params.length === 1) return `${params} -> ${body}`;
|
|
1507
|
+
return `(${params}) -> ${body}`;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
case 'Lambda': {
|
|
1511
|
+
const params = node.params.map(p => p.name).join(', ');
|
|
1512
|
+
return `(${params}) -> { /* lambda body */ }`;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
case 'Ternary':
|
|
1516
|
+
return `(${this.expr(node.condition)} ? ${this.expr(node.consequent)} : ${this.expr(node.alternate)})`;
|
|
1517
|
+
|
|
1518
|
+
case 'Pipe':
|
|
1519
|
+
return this.generatePipe(node);
|
|
1520
|
+
|
|
1521
|
+
default:
|
|
1522
|
+
return `/* expr:${node.type} */ null`;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
mapIdentifier(name) {
|
|
1527
|
+
const map = {
|
|
1528
|
+
'null': 'null',
|
|
1529
|
+
'undefined': 'null',
|
|
1530
|
+
'true': 'true',
|
|
1531
|
+
'false': 'false',
|
|
1532
|
+
'this': 'this',
|
|
1533
|
+
};
|
|
1534
|
+
return map[name] || name;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
mapBinaryOp(op) {
|
|
1538
|
+
const map = {
|
|
1539
|
+
'===': '==',
|
|
1540
|
+
'!==': '!=',
|
|
1541
|
+
'==': '==',
|
|
1542
|
+
'!=': '!=',
|
|
1543
|
+
'&&': '&&',
|
|
1544
|
+
'||': '||',
|
|
1545
|
+
'and': '&&',
|
|
1546
|
+
'or': '||',
|
|
1547
|
+
'not': '!',
|
|
1548
|
+
'>': '>',
|
|
1549
|
+
'<': '<',
|
|
1550
|
+
'>=': '>=',
|
|
1551
|
+
'<=': '<=',
|
|
1552
|
+
'+': '+',
|
|
1553
|
+
'-': '-',
|
|
1554
|
+
'*': '*',
|
|
1555
|
+
'/': '/',
|
|
1556
|
+
'%': '%',
|
|
1557
|
+
'**': 'Math.pow',
|
|
1558
|
+
'instanceof': 'instanceof',
|
|
1559
|
+
'in': '/* in */',
|
|
1560
|
+
};
|
|
1561
|
+
if (op === '**') return '/* ** */';
|
|
1562
|
+
return map[op] || op;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
mapMemberAccess(node) {
|
|
1566
|
+
const obj = this.expr(node.object);
|
|
1567
|
+
const prop = node.property;
|
|
1568
|
+
|
|
1569
|
+
// Map common JS properties to Java
|
|
1570
|
+
if (prop === 'length') return `${obj}.length`;
|
|
1571
|
+
if (prop === 'size') return `${obj}.size()`;
|
|
1572
|
+
if (prop === 'toString') return `String.valueOf(${obj})`;
|
|
1573
|
+
|
|
1574
|
+
// console methods
|
|
1575
|
+
if (obj === 'console' && prop === 'log') return 'System.out.println';
|
|
1576
|
+
if (obj === 'console' && prop === 'error') return 'System.err.println';
|
|
1577
|
+
if (obj === 'console' && prop === 'warn') return 'System.err.println';
|
|
1578
|
+
|
|
1579
|
+
// JSON
|
|
1580
|
+
if (obj === 'JSON' && prop === 'parse') return '/* JSON.parse */';
|
|
1581
|
+
if (obj === 'JSON' && prop === 'stringify') return '/* JSON.stringify */';
|
|
1582
|
+
|
|
1583
|
+
// Math
|
|
1584
|
+
if (obj === 'Math') return `Math.${prop}`;
|
|
1585
|
+
|
|
1586
|
+
// Object static
|
|
1587
|
+
if (obj === 'Object' && prop === 'keys') return '/* Object.keys */';
|
|
1588
|
+
if (obj === 'Object' && prop === 'values') return '/* Object.values */';
|
|
1589
|
+
if (obj === 'Object' && prop === 'entries') return '/* Object.entries */';
|
|
1590
|
+
|
|
1591
|
+
// Array static
|
|
1592
|
+
if (obj === 'Array' && prop === 'isArray') return '/* Array.isArray */';
|
|
1593
|
+
|
|
1594
|
+
// Date
|
|
1595
|
+
if (obj === 'Date' && prop === 'now') return 'System.currentTimeMillis';
|
|
1596
|
+
|
|
1597
|
+
return `${obj}.${prop}`;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
generateCall(node) {
|
|
1601
|
+
const args = node.args.map(a => this.expr(a)).join(', ');
|
|
1602
|
+
|
|
1603
|
+
if (node.callee.type === 'Identifier') {
|
|
1604
|
+
const name = node.callee.name;
|
|
1605
|
+
|
|
1606
|
+
if (name === 'parseInt') return `Integer.parseInt(${args})`;
|
|
1607
|
+
if (name === 'parseFloat') return `Double.parseDouble(${args})`;
|
|
1608
|
+
if (name === 'String') return `String.valueOf(${args})`;
|
|
1609
|
+
if (name === 'Number') return `Double.valueOf(${args})`;
|
|
1610
|
+
if (name === 'Boolean') return `Boolean.valueOf(${args})`;
|
|
1611
|
+
if (name === 'isNaN') return `Double.isNaN(${args})`;
|
|
1612
|
+
if (name === 'setTimeout') return `/* setTimeout - use Timer */ null`;
|
|
1613
|
+
if (name === 'setInterval') return `/* setInterval - use Timer */ null`;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (node.callee.type === 'MemberAccess') {
|
|
1617
|
+
const obj = this.expr(node.callee.object);
|
|
1618
|
+
const prop = node.callee.property;
|
|
1619
|
+
|
|
1620
|
+
// Array/list methods
|
|
1621
|
+
if (prop === 'push') return `${obj}.add(${args})`;
|
|
1622
|
+
if (prop === 'pop') return `${obj}.remove(${obj}.size() - 1)`;
|
|
1623
|
+
if (prop === 'shift') return `${obj}.remove(0)`;
|
|
1624
|
+
if (prop === 'indexOf') return `${obj}.indexOf(${args})`;
|
|
1625
|
+
if (prop === 'includes' || prop === 'contains') return `${obj}.contains(${args})`;
|
|
1626
|
+
if (prop === 'join') return `String.join(${args}, ${obj})`;
|
|
1627
|
+
if (prop === 'forEach') return `${obj}.forEach(${args})`;
|
|
1628
|
+
if (prop === 'map') {
|
|
1629
|
+
this.addImport('java.util.stream.Collectors');
|
|
1630
|
+
return `${obj}.stream().map(${args}).collect(Collectors.toList())`;
|
|
1631
|
+
}
|
|
1632
|
+
if (prop === 'filter') {
|
|
1633
|
+
this.addImport('java.util.stream.Collectors');
|
|
1634
|
+
return `${obj}.stream().filter(${args}).collect(Collectors.toList())`;
|
|
1635
|
+
}
|
|
1636
|
+
if (prop === 'find') return `${obj}.stream().filter(${args}).findFirst().orElse(null)`;
|
|
1637
|
+
if (prop === 'some') return `${obj}.stream().anyMatch(${args})`;
|
|
1638
|
+
if (prop === 'every') return `${obj}.stream().allMatch(${args})`;
|
|
1639
|
+
if (prop === 'reduce') return `${obj}.stream().reduce(${args})`;
|
|
1640
|
+
if (prop === 'sort') {
|
|
1641
|
+
this.addImport('java.util.Collections');
|
|
1642
|
+
return `Collections.sort(${obj})`;
|
|
1643
|
+
}
|
|
1644
|
+
if (prop === 'reverse') {
|
|
1645
|
+
this.addImport('java.util.Collections');
|
|
1646
|
+
return `Collections.reverse(${obj})`;
|
|
1647
|
+
}
|
|
1648
|
+
if (prop === 'flat') return `${obj}.stream().flatMap(java.util.Collection::stream).collect(java.util.stream.Collectors.toList())`;
|
|
1649
|
+
if (prop === 'concat') {
|
|
1650
|
+
this.addImport('java.util.stream.Stream');
|
|
1651
|
+
return `Stream.concat(${obj}.stream(), ${args}.stream()).collect(java.util.stream.Collectors.toList())`;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// String methods
|
|
1655
|
+
if (prop === 'split') return `${obj}.split(${args})`;
|
|
1656
|
+
if (prop === 'trim') return `${obj}.trim()`;
|
|
1657
|
+
if (prop === 'toUpperCase') return `${obj}.toUpperCase()`;
|
|
1658
|
+
if (prop === 'toLowerCase') return `${obj}.toLowerCase()`;
|
|
1659
|
+
if (prop === 'startsWith') return `${obj}.startsWith(${args})`;
|
|
1660
|
+
if (prop === 'endsWith') return `${obj}.endsWith(${args})`;
|
|
1661
|
+
if (prop === 'replace') return `${obj}.replace(${args})`;
|
|
1662
|
+
if (prop === 'replaceAll') return `${obj}.replaceAll(${args})`;
|
|
1663
|
+
if (prop === 'charAt') return `${obj}.charAt(${args})`;
|
|
1664
|
+
if (prop === 'substring') return `${obj}.substring(${args})`;
|
|
1665
|
+
if (prop === 'repeat') return `${obj}.repeat(${args})`;
|
|
1666
|
+
if (prop === 'padStart') return `String.format("%" + ${args} + "s", ${obj})`;
|
|
1667
|
+
if (prop === 'padEnd') return `String.format("%-" + ${args} + "s", ${obj})`;
|
|
1668
|
+
|
|
1669
|
+
// Object methods
|
|
1670
|
+
if (obj === 'Object' && prop === 'keys') return `new java.util.ArrayList<>(((Map<?, ?>) ${args}).keySet())`;
|
|
1671
|
+
if (obj === 'Object' && prop === 'values') return `new java.util.ArrayList<>(((Map<?, ?>) ${args}).values())`;
|
|
1672
|
+
if (obj === 'Object' && prop === 'entries') return `new java.util.ArrayList<>(((Map<?, ?>) ${args}).entrySet())`;
|
|
1673
|
+
|
|
1674
|
+
// Promise
|
|
1675
|
+
if (obj === 'Promise' && prop === 'all') {
|
|
1676
|
+
this.addImport('java.util.concurrent.CompletableFuture');
|
|
1677
|
+
return `CompletableFuture.allOf(${args})`;
|
|
1678
|
+
}
|
|
1679
|
+
if (obj === 'Promise' && prop === 'resolve') return `CompletableFuture.completedFuture(${args})`;
|
|
1680
|
+
|
|
1681
|
+
// Date
|
|
1682
|
+
if (obj === 'Date' && prop === 'now') return 'System.currentTimeMillis()';
|
|
1683
|
+
|
|
1684
|
+
// console
|
|
1685
|
+
if (obj === 'console' && prop === 'log') return `System.out.println(${args})`;
|
|
1686
|
+
if (obj === 'console' && prop === 'error') return `System.err.println(${args})`;
|
|
1687
|
+
if (obj === 'console' && prop === 'warn') return `System.err.println(${args})`;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
const callee = this.expr(node.callee);
|
|
1691
|
+
return `${callee}(${args})`;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
generateString(strData) {
|
|
1695
|
+
if (!strData || !strData.parts) return '""';
|
|
1696
|
+
|
|
1697
|
+
const hasInterpolation = strData.parts.some(p => p.type === 'expr');
|
|
1698
|
+
|
|
1699
|
+
if (!hasInterpolation) {
|
|
1700
|
+
const raw = strData.parts.map(p => p.value).join('');
|
|
1701
|
+
return JSON.stringify(raw);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// Use String.format for interpolation
|
|
1705
|
+
let formatStr = '';
|
|
1706
|
+
const formatArgs = [];
|
|
1707
|
+
|
|
1708
|
+
for (const part of strData.parts) {
|
|
1709
|
+
if (part.type === 'text') {
|
|
1710
|
+
formatStr += part.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/%/g, '%%');
|
|
1711
|
+
} else {
|
|
1712
|
+
formatStr += '%s';
|
|
1713
|
+
const exprCode = part.value.replace(/\bself\b/g, 'this');
|
|
1714
|
+
formatArgs.push(exprCode);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
if (formatArgs.length === 0) {
|
|
1719
|
+
return `"${formatStr}"`;
|
|
1720
|
+
}
|
|
1721
|
+
return `String.format("${formatStr}", ${formatArgs.join(', ')})`;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
rawString(strData) {
|
|
1725
|
+
if (!strData) return '';
|
|
1726
|
+
if (strData.raw !== null && strData.raw !== undefined) return strData.raw;
|
|
1727
|
+
if (strData.parts) return strData.parts.map(p => p.value).join('');
|
|
1728
|
+
return '';
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
stringValue(strData) {
|
|
1732
|
+
if (!strData || !strData.parts) return '""';
|
|
1733
|
+
if (strData.raw !== null && strData.raw !== undefined) {
|
|
1734
|
+
return JSON.stringify(strData.raw);
|
|
1735
|
+
}
|
|
1736
|
+
return this.generateString(strData);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
generatePipe(node) {
|
|
1740
|
+
const steps = [];
|
|
1741
|
+
let current = node;
|
|
1742
|
+
while (current.type === 'Pipe') {
|
|
1743
|
+
steps.unshift(current.right);
|
|
1744
|
+
current = current.left;
|
|
1745
|
+
}
|
|
1746
|
+
steps.unshift(current);
|
|
1747
|
+
|
|
1748
|
+
const STREAM_METHODS = new Set(['filter', 'map', 'reduce', 'find', 'some', 'every', 'sort', 'reverse', 'join']);
|
|
1749
|
+
|
|
1750
|
+
let result = this.expr(steps[0]);
|
|
1751
|
+
for (let i = 1; i < steps.length; i++) {
|
|
1752
|
+
const step = steps[i];
|
|
1753
|
+
if (step.type === 'Call' && step.callee.type === 'Identifier') {
|
|
1754
|
+
const name = step.callee.name;
|
|
1755
|
+
const args = step.args.map(a => this.expr(a)).join(', ');
|
|
1756
|
+
|
|
1757
|
+
if (name === 'filter') {
|
|
1758
|
+
this.addImport('java.util.stream.Collectors');
|
|
1759
|
+
result = `${result}.stream().filter(${args}).collect(Collectors.toList())`;
|
|
1760
|
+
} else if (name === 'map') {
|
|
1761
|
+
this.addImport('java.util.stream.Collectors');
|
|
1762
|
+
result = `${result}.stream().map(${args}).collect(Collectors.toList())`;
|
|
1763
|
+
} else if (name === 'reduce') {
|
|
1764
|
+
result = `${result}.stream().reduce(${args})`;
|
|
1765
|
+
} else if (name === 'sort') {
|
|
1766
|
+
result = `${result}.stream().sorted().collect(java.util.stream.Collectors.toList())`;
|
|
1767
|
+
} else if (name === 'reverse') {
|
|
1768
|
+
this.addImport('java.util.Collections');
|
|
1769
|
+
result = `Collections.reverse(${result})`;
|
|
1770
|
+
} else if (name === 'join') {
|
|
1771
|
+
result = `String.join(${args}, ${result})`;
|
|
1772
|
+
} else {
|
|
1773
|
+
result = `${name}(${result}${args ? ', ' + args : ''})`;
|
|
1774
|
+
}
|
|
1775
|
+
} else if (step.type === 'Identifier') {
|
|
1776
|
+
result = `${step.name}(${result})`;
|
|
1777
|
+
} else if (step.type === 'Call') {
|
|
1778
|
+
const callee = this.expr(step.callee);
|
|
1779
|
+
const args = step.args.map(a => this.expr(a)).join(', ');
|
|
1780
|
+
result = `${callee}(${result}${args ? ', ' + args : ''})`;
|
|
1781
|
+
} else {
|
|
1782
|
+
result = `(${this.expr(step)}).apply(${result})`;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
return result;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// ===== Utilities =====
|
|
1789
|
+
|
|
1790
|
+
bodyUsesAwait(body) {
|
|
1791
|
+
for (const stmt of body) {
|
|
1792
|
+
if (this.stmtUsesAwait(stmt)) return true;
|
|
1793
|
+
}
|
|
1794
|
+
return false;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
stmtUsesAwait(node) {
|
|
1798
|
+
if (!node) return false;
|
|
1799
|
+
const json = JSON.stringify(node);
|
|
1800
|
+
return json.includes('"Await"') || json.includes('"AwaitAll"');
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
statementToString(stmt) {
|
|
1804
|
+
const saved = this.output;
|
|
1805
|
+
const savedIndent = this.indent;
|
|
1806
|
+
this.output = [];
|
|
1807
|
+
this.indent = 0;
|
|
1808
|
+
this.visitStatement(stmt);
|
|
1809
|
+
const result = this.output.join('\n');
|
|
1810
|
+
this.output = saved;
|
|
1811
|
+
this.indent = savedIndent;
|
|
1812
|
+
return result;
|
|
1813
|
+
}
|
|
1814
|
+
}
|