ether-code 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -0
  3. package/cli/compiler.js +298 -0
  4. package/cli/ether.js +532 -0
  5. package/cli/watcher.js +106 -0
  6. package/generators/css-generator.js +583 -0
  7. package/generators/graphql-generator.js +868 -0
  8. package/generators/html-generator.js +745 -0
  9. package/generators/js-generator.js +909 -0
  10. package/generators/node-generator.js +467 -0
  11. package/generators/php-generator.js +706 -0
  12. package/generators/python-generator.js +913 -0
  13. package/generators/react-generator.js +599 -0
  14. package/generators/ruby-generator.js +904 -0
  15. package/generators/sql-generator.js +988 -0
  16. package/generators/ts-generator.js +569 -0
  17. package/i18n/i18n-css.json +743 -0
  18. package/i18n/i18n-graphql.json +1531 -0
  19. package/i18n/i18n-html.json +572 -0
  20. package/i18n/i18n-js.json +2790 -0
  21. package/i18n/i18n-node.json +2442 -0
  22. package/i18n/i18n-php.json +4306 -0
  23. package/i18n/i18n-python.json +3080 -0
  24. package/i18n/i18n-react.json +1784 -0
  25. package/i18n/i18n-ruby.json +1858 -0
  26. package/i18n/i18n-sql.json +3466 -0
  27. package/i18n/i18n-ts.json +442 -0
  28. package/lexer/ether-lexer.js +728 -0
  29. package/lexer/tokens.js +292 -0
  30. package/package.json +45 -0
  31. package/parsers/ast-css.js +545 -0
  32. package/parsers/ast-graphql.js +424 -0
  33. package/parsers/ast-html.js +886 -0
  34. package/parsers/ast-js.js +750 -0
  35. package/parsers/ast-node.js +2440 -0
  36. package/parsers/ast-php.js +957 -0
  37. package/parsers/ast-react.js +580 -0
  38. package/parsers/ast-ruby.js +895 -0
  39. package/parsers/ast-ts.js +1352 -0
  40. package/parsers/css-parser.js +1981 -0
  41. package/parsers/graphql-parser.js +2011 -0
  42. package/parsers/html-parser.js +1181 -0
  43. package/parsers/js-parser.js +2564 -0
  44. package/parsers/node-parser.js +2644 -0
  45. package/parsers/php-parser.js +3037 -0
  46. package/parsers/react-parser.js +1035 -0
  47. package/parsers/ruby-parser.js +2680 -0
  48. package/parsers/ts-parser.js +3881 -0
@@ -0,0 +1,904 @@
1
+ const fs = require('fs')
2
+
3
+ class RubyGenerator {
4
+ constructor(i18nPath = null) {
5
+ this.i18n = null
6
+ this.indent = 0
7
+ this.output = ''
8
+ this.keywordMap = {}
9
+ this.methodMap = {}
10
+
11
+ if (i18nPath) {
12
+ this.loadI18n(i18nPath)
13
+ }
14
+ }
15
+
16
+ loadI18n(filePath) {
17
+ const content = fs.readFileSync(filePath, 'utf-8')
18
+ this.i18n = JSON.parse(content)
19
+ this.buildMaps()
20
+ }
21
+
22
+ buildMaps() {
23
+ this.keywordMap = {}
24
+
25
+ if (!this.i18n) return
26
+
27
+ const addToMap = (map, section) => {
28
+ if (!section) return
29
+ for (const [key, translations] of Object.entries(section)) {
30
+ if (translations && translations.fr && translations.ruby) {
31
+ map[translations.fr.toLowerCase()] = translations.ruby
32
+ }
33
+ }
34
+ }
35
+
36
+ for (const sectionName of Object.keys(this.i18n)) {
37
+ addToMap(this.keywordMap, this.i18n[sectionName])
38
+ }
39
+ }
40
+
41
+ translate(word) {
42
+ if (!word) return ''
43
+ const lower = word.toLowerCase()
44
+ return this.keywordMap[lower] || this.translateGeneric(word)
45
+ }
46
+
47
+ translateGeneric(word) {
48
+ const translations = {
49
+ 'si': 'if',
50
+ 'sinon': 'else',
51
+ 'sinon si': 'elsif',
52
+ 'sauf si': 'unless',
53
+ 'selon': 'case',
54
+ 'quand': 'when',
55
+ 'pour': 'for',
56
+ 'dans': 'in',
57
+ 'tant que': 'while',
58
+ 'jusqua': 'until',
59
+ 'faire': 'do',
60
+ 'fin': 'end',
61
+ 'définir': 'def',
62
+ 'classe': 'class',
63
+ 'module': 'module',
64
+ 'retourner': 'return',
65
+ 'produire': 'yield',
66
+ 'sortir': 'break',
67
+ 'continuer': 'next',
68
+ 'refaire': 'redo',
69
+ 'reessayer': 'retry',
70
+ 'lever': 'raise',
71
+ 'sauver': 'rescue',
72
+ 'assurer': 'ensure',
73
+ 'commencer': 'begin',
74
+ 'et': 'and',
75
+ 'ou': 'or',
76
+ 'non': 'not',
77
+ 'vrai': 'true',
78
+ 'faux': 'false',
79
+ 'nul': 'nil',
80
+ 'soi': 'self',
81
+ 'super': 'super',
82
+ 'hériter': '<',
83
+ 'inclure': 'include',
84
+ 'etendre': 'extend',
85
+ 'prepend': 'prepend',
86
+ 'public': 'public',
87
+ 'privé': 'private',
88
+ 'protégé': 'protected',
89
+ 'attr lecteur': 'attr_reader',
90
+ 'attr ecrivain': 'attr_writer',
91
+ 'attr accesseur': 'attr_accessor',
92
+ 'initialiser': 'initialize',
93
+ 'nouveau': 'new',
94
+ 'requiert': 'require',
95
+ 'requiert relatif': 'require_relative',
96
+ 'charger': 'load',
97
+ 'afficher': 'puts',
98
+ 'imprimer': 'print',
99
+ 'obtenir': 'gets',
100
+ 'longueur': 'length',
101
+ 'taille': 'size',
102
+ 'vide': 'empty?',
103
+ 'inclut': 'include?',
104
+ 'premier': 'first',
105
+ 'dernier': 'last',
106
+ 'pousser': 'push',
107
+ 'retirer': 'pop',
108
+ 'decaler': 'shift',
109
+ 'insérer début': 'unshift',
110
+ 'ajouter': '<<',
111
+ 'concatener': '+',
112
+ 'aplatir': 'flatten',
113
+ 'compacter': 'compact',
114
+ 'unique': 'uniq',
115
+ 'trier': 'sort',
116
+ 'inverser': 'reverse',
117
+ 'chaque': 'each',
118
+ 'mapper': 'map',
119
+ 'collecter': 'collect',
120
+ 'sélectionner': 'select',
121
+ 'rejeter': 'reject',
122
+ 'trouver': 'find',
123
+ 'detecter': 'detect',
124
+ 'réduire': 'reduce',
125
+ 'injecter': 'inject',
126
+ 'tout': 'all?',
127
+ 'quelconque': 'any?',
128
+ 'aucun': 'none?',
129
+ 'compter': 'count',
130
+ 'grouper par': 'group_by',
131
+ 'trier par': 'sort_by',
132
+ 'min par': 'min_by',
133
+ 'max par': 'max_by',
134
+ 'avec index': 'each_with_index',
135
+ 'avec objet': 'each_with_object',
136
+ 'fois': 'times',
137
+ 'jusqua iter': 'upto',
138
+ 'descendant': 'downto',
139
+ 'etape': 'step',
140
+ 'majuscules': 'upcase',
141
+ 'minuscules': 'downcase',
142
+ 'capitaliser': 'capitalize',
143
+ 'inverser chaîne': 'reverse',
144
+ 'supprimer espaces': 'strip',
145
+ 'supprimer gauche': 'lstrip',
146
+ 'supprimer droite': 'rstrip',
147
+ 'diviser': 'split',
148
+ 'joindre': 'join',
149
+ 'remplacer': 'gsub',
150
+ 'remplacer premier': 'sub',
151
+ 'correspondre': 'match',
152
+ 'commence par': 'start_with?',
153
+ 'finit par': 'end_with?',
154
+ 'contient': 'include?',
155
+ 'vers entier': 'to_i',
156
+ 'vers flottant': 'to_f',
157
+ 'vers chaîne': 'to_s',
158
+ 'vers symbole': 'to_sym',
159
+ 'vers tableau': 'to_a',
160
+ 'vers hash': 'to_h',
161
+ 'clés': 'keys',
162
+ 'valeurs': 'values',
163
+ 'fusionner': 'merge',
164
+ 'chercher': 'fetch',
165
+ 'stocker': 'store',
166
+ 'supprimer clé': 'delete',
167
+ 'a clé': 'has_key?',
168
+ 'a valeur': 'has_value?',
169
+ 'ouvrir': 'open',
170
+ 'lire': 'read',
171
+ 'écrire': 'write',
172
+ 'fermer': 'close',
173
+ 'chaque ligne': 'each_line',
174
+ 'lire lignes': 'readlines',
175
+ 'existe': 'exist?',
176
+ 'fichier': 'file?',
177
+ 'dossier': 'directory?',
178
+ 'supprimer fichier': 'delete',
179
+ 'renommer': 'rename',
180
+ 'maintenant': 'Time.now',
181
+ 'aujourdhui': 'Date.today',
182
+ 'analyser date': 'parse',
183
+ 'formater': 'strftime',
184
+ 'année': 'year',
185
+ 'mois': 'month',
186
+ 'jour': 'day',
187
+ 'heure': 'hour',
188
+ 'minute': 'min',
189
+ 'seconde': 'sec',
190
+ 'aléatoire': 'rand',
191
+ 'absolu': 'abs',
192
+ 'arrondir': 'round',
193
+ 'plancher': 'floor',
194
+ 'plafond': 'ceil',
195
+ 'racine': 'sqrt',
196
+ 'puissance': '**',
197
+ 'lambda': 'lambda',
198
+ 'proc': 'Proc.new',
199
+ 'appeler': 'call',
200
+ 'bloc donne': 'block_given?',
201
+ 'méthode': 'method',
202
+ 'envoyer': 'send',
203
+ 'repondre a': 'respond_to?',
204
+ 'instance de': 'is_a?',
205
+ 'sorte de': 'kind_of?',
206
+ 'classe de': 'class',
207
+ 'methodes': 'methods',
208
+ 'variables instance': 'instance_variables',
209
+ 'variables classe': 'class_variables',
210
+ 'constantes': 'constants',
211
+ 'geler': 'freeze',
212
+ 'gele': 'frozen?',
213
+ 'dupliquer': 'dup',
214
+ 'cloner': 'clone',
215
+ 'inspecter': 'inspect',
216
+ 'symbole': ':',
217
+ 'hash rocket': '=>',
218
+ 'range': '..',
219
+ 'range exclusif': '...',
220
+ 'splat': '*',
221
+ 'double splat': '**',
222
+ 'bloc arg': '&'
223
+ }
224
+
225
+ const lower = word.toLowerCase()
226
+ return translations[lower] || word
227
+ }
228
+
229
+ generate(ast) {
230
+ this.output = ''
231
+ this.indent = 0
232
+
233
+ if (Array.isArray(ast)) {
234
+ for (const node of ast) {
235
+ const result = this.generateNode(node)
236
+ if (result !== undefined && result !== '' && this.output === '') {
237
+ return result
238
+ }
239
+ }
240
+ } else if (ast && ast.type) {
241
+ const result = this.generateNode(ast)
242
+ if (result !== undefined && result !== '' && this.output === '') {
243
+ return result
244
+ }
245
+ } else if (ast && ast.body) {
246
+ for (const node of ast.body) {
247
+ const result = this.generateNode(node)
248
+ if (result !== undefined && result !== '' && this.output === '') {
249
+ return result
250
+ }
251
+ }
252
+ }
253
+
254
+ return this.output.trim()
255
+ }
256
+
257
+ generateNode(node) {
258
+ if (!node) return ''
259
+
260
+ switch (node.type) {
261
+ case 'RequireStatement':
262
+ return this.generateRequire(node)
263
+ case 'ModuleDeclaration':
264
+ return this.generateModule(node)
265
+ case 'ClassDeclaration':
266
+ return this.generateClass(node)
267
+ case 'MethodDef':
268
+ case 'MethodDeclaration':
269
+ case 'FunctionDeclaration':
270
+ return this.generateMethod(node)
271
+ case 'IfStatement':
272
+ return this.generateIf(node)
273
+ case 'UnlessStatement':
274
+ return this.generateUnless(node)
275
+ case 'CaseStatement':
276
+ return this.generateCase(node)
277
+ case 'WhileStatement':
278
+ return this.generateWhile(node)
279
+ case 'UntilStatement':
280
+ return this.generateUntil(node)
281
+ case 'ForStatement':
282
+ return this.generateFor(node)
283
+ case 'EachStatement':
284
+ return this.generateEach(node)
285
+ case 'TimesStatement':
286
+ return this.generateTimes(node)
287
+ case 'BeginRescue':
288
+ case 'BeginStatement':
289
+ case 'TryStatement':
290
+ return this.generateBeginRescue(node)
291
+ case 'ReturnStatement':
292
+ return this.generateReturn(node)
293
+ case 'YieldStatement':
294
+ return this.generateYield(node)
295
+ case 'RaiseStatement':
296
+ return this.generateRaise(node)
297
+ case 'BreakStatement':
298
+ this.writeLine('break')
299
+ return
300
+ case 'NextStatement':
301
+ this.writeLine('next')
302
+ return
303
+ case 'ExpressionStatement':
304
+ return this.generateExpressionStatement(node)
305
+ case 'AssignmentExpression':
306
+ return this.generateAssignment(node)
307
+ case 'CallExpression':
308
+ return this.generateCall(node)
309
+ case 'BlockExpression':
310
+ return this.generateBlock(node)
311
+ case 'LambdaExpression':
312
+ return this.generateLambda(node)
313
+ case 'ArrayExpression':
314
+ return this.generateArray(node)
315
+ case 'HashExpression':
316
+ return this.generateHash(node)
317
+ case 'RangeExpression':
318
+ return this.generateRange(node)
319
+ case 'SymbolExpression':
320
+ return this.generateSymbol(node)
321
+ case 'StringInterpolation':
322
+ case 'InterpolatedString':
323
+ return this.generateStringInterpolation(node)
324
+ case 'BinaryExpression':
325
+ return this.generateBinary(node)
326
+ case 'UnaryExpression':
327
+ return this.generateUnary(node)
328
+ case 'TernaryExpression':
329
+ case 'ConditionalExpression':
330
+ return this.generateTernary(node)
331
+ case 'MethodCallExpression':
332
+ return this.generateMethodCall(node)
333
+ case 'Identifier':
334
+ return this.translateIdentifier(node.name)
335
+ case 'Literal':
336
+ return this.generateLiteral(node)
337
+ default:
338
+ if (node.name && !node.type) {
339
+ return this.translateIdentifier(node.name)
340
+ }
341
+ return ''
342
+ }
343
+ }
344
+
345
+ generateBinary(node) {
346
+ const left = this.generateNode(node.left)
347
+ const right = this.generateNode(node.right)
348
+ const op = node.operator
349
+ return `${left} ${op} ${right}`
350
+ }
351
+
352
+ generateUnary(node) {
353
+ const operand = this.generateNode(node.operand || node.argument)
354
+ const op = node.operator
355
+ if (op === 'not' || op === '!') {
356
+ return `${op === 'not' ? 'not ' : '!'}${operand}`
357
+ }
358
+ return `${op}${operand}`
359
+ }
360
+
361
+ generateTernary(node) {
362
+ const test = this.generateNode(node.test)
363
+ const consequent = this.generateNode(node.consequent)
364
+ const alternate = this.generateNode(node.alternate)
365
+ return `${test} ? ${consequent} : ${alternate}`
366
+ }
367
+
368
+ generateMethodCall(node) {
369
+ const receiver = this.generateNode(node.receiver)
370
+ const method = this.translate(node.method)
371
+ const args = (node.arguments || []).map(a => this.generateNode(a)).join(', ')
372
+ const safe = node.safe ? '&.' : '.'
373
+ const argsStr = args ? `(${args})` : ''
374
+
375
+ if (node.block) {
376
+ const blockParams = node.block.params ? '|' + node.block.params.map(p => p.name || p).join(', ') + '|' : ''
377
+ const blockBody = (node.block.body || []).map(s => this.generateNode(s)).join('; ')
378
+ if (node.block.body?.length === 1) {
379
+ return `${receiver}${safe}${method}${argsStr} { ${blockParams} ${blockBody} }`
380
+ }
381
+ return `${receiver}${safe}${method}${argsStr} do ${blockParams}\n${blockBody}\nend`
382
+ }
383
+
384
+ return `${receiver}${safe}${method}${argsStr}`
385
+ }
386
+
387
+ generateRequire(node) {
388
+ const path = typeof node.path === 'string' ? node.path : this.generateNode(node.path)
389
+ const method = node.relative ? 'require_relative' : 'require'
390
+ this.writeLine(`${method} '${path}'`)
391
+ }
392
+
393
+ generateModule(node) {
394
+ const name = node.name
395
+ this.writeLine(`module ${name}`)
396
+ this.indent++
397
+
398
+ const body = node.body?.body || node.body || []
399
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
400
+ this.generateNode(stmt)
401
+ }
402
+
403
+ this.indent--
404
+ this.writeLine('end')
405
+ this.writeLine('')
406
+ }
407
+
408
+ generateClass(node) {
409
+ const name = node.name || node.id?.name
410
+ let declaration = `class ${name}`
411
+
412
+ if (node.superclass || node.extends) {
413
+ declaration += ` < ${node.superclass || node.extends}`
414
+ }
415
+
416
+ this.writeLine(declaration)
417
+ this.indent++
418
+
419
+ if (node.includes) {
420
+ for (const inc of node.includes) {
421
+ this.writeLine(`include ${inc}`)
422
+ }
423
+ this.writeLine('')
424
+ }
425
+
426
+ const attrs = node.attrs || node.attributes || []
427
+ if (attrs.length > 0) {
428
+ const accessors = attrs.filter(a => a.accessor === 'accessor')
429
+ const readers = attrs.filter(a => a.accessor === 'reader')
430
+ const writers = attrs.filter(a => a.accessor === 'writer')
431
+
432
+ if (accessors.length > 0) {
433
+ this.writeLine(`attr_accessor ${accessors.map(a => ':' + a.name).join(', ')}`)
434
+ }
435
+ if (readers.length > 0) {
436
+ this.writeLine(`attr_reader ${readers.map(a => ':' + a.name).join(', ')}`)
437
+ }
438
+ if (writers.length > 0) {
439
+ this.writeLine(`attr_writer ${writers.map(a => ':' + a.name).join(', ')}`)
440
+ }
441
+ }
442
+
443
+ const body = node.body?.body || node.body || []
444
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
445
+ this.generateNode(stmt)
446
+ }
447
+
448
+ this.indent--
449
+ this.writeLine('end')
450
+ }
451
+
452
+ generateMethod(node) {
453
+ const name = this.translate(node.name || node.id?.name || '')
454
+ const params = this.generateParams(node.params || node.args || [])
455
+ const selfMethod = node.static || node.classMethod ? 'self.' : ''
456
+
457
+ this.writeLine(`def ${selfMethod}${name}${params ? '(' + params + ')' : ''}`)
458
+ this.indent++
459
+
460
+ const body = node.body?.body || node.body || []
461
+ if (body.length === 0) {
462
+ this.writeLine('nil')
463
+ } else {
464
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
465
+ this.generateNode(stmt)
466
+ }
467
+ }
468
+
469
+ this.indent--
470
+ this.writeLine('end')
471
+ }
472
+
473
+ generateParams(params) {
474
+ return params.map(p => {
475
+ let param = ''
476
+
477
+ if (p.splat) param += '*'
478
+ if (p.doubleSplat) param += '**'
479
+ if (p.block) param += '&'
480
+
481
+ param += this.translate(p.name || p.arg || p)
482
+
483
+ if (p.default !== undefined) {
484
+ param += ' = ' + this.generateNode(p.default)
485
+ }
486
+
487
+ return param
488
+ }).join(', ')
489
+ }
490
+
491
+ generateIf(node) {
492
+ const test = this.generateNode(node.test)
493
+
494
+ if (node.modifier) {
495
+ const body = this.generateNode(node.body)
496
+ this.writeLine(`${body} if ${test}`)
497
+ return
498
+ }
499
+
500
+ this.writeLine(`if ${test}`)
501
+ this.indent++
502
+
503
+ const consequent = node.consequent?.body || node.consequent || node.body || []
504
+ for (const stmt of (Array.isArray(consequent) ? consequent : [consequent])) {
505
+ this.generateNode(stmt)
506
+ }
507
+
508
+ this.indent--
509
+
510
+ if (node.alternate || node.elsif) {
511
+ const alt = node.alternate || node.elsif
512
+ if (alt.type === 'IfStatement') {
513
+ const altTest = this.generateNode(alt.test)
514
+ this.writeLine(`elsif ${altTest}`)
515
+ this.indent++
516
+ const altBody = alt.consequent?.body || alt.consequent || alt.body || []
517
+ for (const stmt of (Array.isArray(altBody) ? altBody : [altBody])) {
518
+ this.generateNode(stmt)
519
+ }
520
+ this.indent--
521
+ if (alt.alternate) {
522
+ this.generateNode({ type: 'IfStatement', alternate: alt.alternate })
523
+ } else {
524
+ this.writeLine('end')
525
+ }
526
+ } else {
527
+ this.writeLine('else')
528
+ this.indent++
529
+ const altBody = Array.isArray(alt) ? alt : (alt.body || [alt])
530
+ for (const stmt of altBody) {
531
+ this.generateNode(stmt)
532
+ }
533
+ this.indent--
534
+ this.writeLine('end')
535
+ }
536
+ } else {
537
+ this.writeLine('end')
538
+ }
539
+ }
540
+
541
+ generateUnless(node) {
542
+ const test = this.generateNode(node.test)
543
+
544
+ if (node.modifier) {
545
+ const body = this.generateNode(node.body)
546
+ this.writeLine(`${body} unless ${test}`)
547
+ return
548
+ }
549
+
550
+ this.writeLine(`unless ${test}`)
551
+ this.indent++
552
+
553
+ const body = node.body?.body || node.body || []
554
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
555
+ this.generateNode(stmt)
556
+ }
557
+
558
+ this.indent--
559
+
560
+ if (node.else) {
561
+ this.writeLine('else')
562
+ this.indent++
563
+ for (const stmt of node.else) {
564
+ this.generateNode(stmt)
565
+ }
566
+ this.indent--
567
+ }
568
+
569
+ this.writeLine('end')
570
+ }
571
+
572
+ generateCase(node) {
573
+ const expr = node.expression ? ' ' + this.generateNode(node.expression) : ''
574
+ this.writeLine(`case${expr}`)
575
+
576
+ for (const when of node.whens || []) {
577
+ const conditions = (when.conditions || [when.condition]).map(c => this.generateNode(c))
578
+ this.writeLine(`when ${conditions.join(', ')}`)
579
+ this.indent++
580
+ for (const stmt of when.body || []) {
581
+ this.generateNode(stmt)
582
+ }
583
+ this.indent--
584
+ }
585
+
586
+ if (node.else) {
587
+ this.writeLine('else')
588
+ this.indent++
589
+ for (const stmt of node.else) {
590
+ this.generateNode(stmt)
591
+ }
592
+ this.indent--
593
+ }
594
+
595
+ this.writeLine('end')
596
+ }
597
+
598
+ generateWhile(node) {
599
+ const test = this.generateNode(node.test)
600
+
601
+ if (node.modifier) {
602
+ const body = this.generateNode(node.body)
603
+ this.writeLine(`${body} while ${test}`)
604
+ return
605
+ }
606
+
607
+ this.writeLine(`while ${test}`)
608
+ this.indent++
609
+
610
+ const body = node.body?.body || node.body || []
611
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
612
+ this.generateNode(stmt)
613
+ }
614
+
615
+ this.indent--
616
+ this.writeLine('end')
617
+ }
618
+
619
+ generateUntil(node) {
620
+ const test = this.generateNode(node.test)
621
+
622
+ this.writeLine(`until ${test}`)
623
+ this.indent++
624
+
625
+ const body = node.body?.body || node.body || []
626
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
627
+ this.generateNode(stmt)
628
+ }
629
+
630
+ this.indent--
631
+ this.writeLine('end')
632
+ }
633
+
634
+ generateFor(node) {
635
+ const variable = this.generateNode(node.variable || node.target)
636
+ const iterable = this.generateNode(node.iterable || node.iter)
637
+
638
+ this.writeLine(`for ${variable} in ${iterable}`)
639
+ this.indent++
640
+
641
+ const body = node.body?.body || node.body || []
642
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
643
+ this.generateNode(stmt)
644
+ }
645
+
646
+ this.indent--
647
+ this.writeLine('end')
648
+ }
649
+
650
+ generateEach(node) {
651
+ const collection = this.generateNode(node.collection)
652
+ const blockParams = (node.params || ['item']).join(', ')
653
+
654
+ this.writeLine(`${collection}.each do |${blockParams}|`)
655
+ this.indent++
656
+
657
+ const body = node.body?.body || node.body || []
658
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
659
+ this.generateNode(stmt)
660
+ }
661
+
662
+ this.indent--
663
+ this.writeLine('end')
664
+ }
665
+
666
+ generateTimes(node) {
667
+ const count = this.generateNode(node.count)
668
+ const variable = node.variable || 'i'
669
+
670
+ this.writeLine(`${count}.times do |${variable}|`)
671
+ this.indent++
672
+
673
+ const body = node.body?.body || node.body || []
674
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
675
+ this.generateNode(stmt)
676
+ }
677
+
678
+ this.indent--
679
+ this.writeLine('end')
680
+ }
681
+
682
+ generateBeginRescue(node) {
683
+ this.writeLine('begin')
684
+ this.indent++
685
+
686
+ const tryBody = node.body?.body || node.body || node.block?.body || []
687
+ for (const stmt of (Array.isArray(tryBody) ? tryBody : [tryBody])) {
688
+ this.generateNode(stmt)
689
+ }
690
+
691
+ this.indent--
692
+
693
+ for (const handler of node.rescues || node.handlers || (node.handler ? [node.handler] : [])) {
694
+ let rescueLine = 'rescue'
695
+ const exceptions = handler.exceptions || (handler.exception ? [handler.exception] : []) || (handler.type ? [handler.type] : [])
696
+ if (exceptions.length > 0) {
697
+ rescueLine += ' ' + exceptions.join(', ')
698
+ if (handler.variable || handler.name) {
699
+ rescueLine += ' => ' + (handler.variable || handler.name)
700
+ }
701
+ }
702
+ this.writeLine(rescueLine)
703
+ this.indent++
704
+
705
+ const handlerBody = handler.body?.body || handler.body || []
706
+ for (const stmt of (Array.isArray(handlerBody) ? handlerBody : [handlerBody])) {
707
+ this.generateNode(stmt)
708
+ }
709
+
710
+ this.indent--
711
+ }
712
+
713
+ if (node.else) {
714
+ this.writeLine('else')
715
+ this.indent++
716
+ for (const stmt of node.else) {
717
+ this.generateNode(stmt)
718
+ }
719
+ this.indent--
720
+ }
721
+
722
+ if (node.ensure || node.finally) {
723
+ this.writeLine('ensure')
724
+ this.indent++
725
+ const ensureBody = node.ensure || node.finally?.body || node.finally || []
726
+ for (const stmt of (Array.isArray(ensureBody) ? ensureBody : [ensureBody])) {
727
+ this.generateNode(stmt)
728
+ }
729
+ this.indent--
730
+ }
731
+
732
+ this.writeLine('end')
733
+ }
734
+
735
+ generateReturn(node) {
736
+ if (node.value || node.argument) {
737
+ const value = this.generateNode(node.value || node.argument)
738
+ this.writeLine(`return ${value}`)
739
+ } else {
740
+ this.writeLine('return')
741
+ }
742
+ }
743
+
744
+ generateYield(node) {
745
+ if (node.arguments && node.arguments.length > 0) {
746
+ const args = node.arguments.map(a => this.generateNode(a)).join(', ')
747
+ this.writeLine(`yield ${args}`)
748
+ } else {
749
+ this.writeLine('yield')
750
+ }
751
+ }
752
+
753
+ generateRaise(node) {
754
+ if (node.exception) {
755
+ const exc = this.generateNode(node.exception)
756
+ if (node.message) {
757
+ this.writeLine(`raise ${exc}, ${this.generateNode(node.message)}`)
758
+ } else {
759
+ this.writeLine(`raise ${exc}`)
760
+ }
761
+ } else {
762
+ this.writeLine('raise')
763
+ }
764
+ }
765
+
766
+ generateExpressionStatement(node) {
767
+ const expr = this.generateNode(node.expression)
768
+ this.writeLine(expr)
769
+ }
770
+
771
+ generateAssignment(node) {
772
+ const target = this.generateNode(node.left || node.target)
773
+ const value = this.generateNode(node.right || node.value)
774
+ const op = node.operator || '='
775
+ return `${target} ${op} ${value}`
776
+ }
777
+
778
+ generateCall(node) {
779
+ const receiver = node.receiver ? this.generateNode(node.receiver) + '.' : ''
780
+ const callee = node.callee?.name || node.callee || node.method || ''
781
+ const method = typeof callee === 'string' ? this.translate(callee) : this.generateNode(callee)
782
+ const args = (node.arguments || node.args || []).map(a => this.generateNode(a))
783
+
784
+ let result = `${receiver}${method}(${args.join(', ')})`
785
+
786
+ if (node.block) {
787
+ result += ' ' + this.generateBlock(node.block)
788
+ }
789
+
790
+ return result
791
+ }
792
+
793
+ generateBlock(node) {
794
+ const params = node.params ? `|${node.params.join(', ')}|` : ''
795
+ const body = node.body?.body || node.body || []
796
+
797
+ if (body.length === 1 && !body[0].type?.includes('Statement')) {
798
+ return `{ ${params} ${this.generateNode(body[0])} }`
799
+ }
800
+
801
+ let result = `do ${params}\n`
802
+ this.indent++
803
+ for (const stmt of (Array.isArray(body) ? body : [body])) {
804
+ result += this.getIndent() + this.generateNode(stmt) + '\n'
805
+ }
806
+ this.indent--
807
+ result += this.getIndent() + 'end'
808
+ return result
809
+ }
810
+
811
+ generateLambda(node) {
812
+ const params = node.params ? `(${this.generateParams(node.params)})` : ''
813
+ const body = this.generateNode(node.body)
814
+ return `-> ${params} { ${body} }`
815
+ }
816
+
817
+ generateArray(node) {
818
+ const elements = (node.elements || []).map(e => this.generateNode(e))
819
+ return `[${elements.join(', ')}]`
820
+ }
821
+
822
+ generateHash(node) {
823
+ const pairs = (node.pairs || node.properties || []).map(p => {
824
+ let key, value
825
+
826
+ if (p.key?.type === 'SymbolExpression') {
827
+ key = (p.key.name || p.key.value) + ':'
828
+ } else if (typeof p.key === 'string' || p.key?.type === 'Literal') {
829
+ const keyVal = typeof p.key === 'string' ? p.key : p.key.value
830
+ if (typeof keyVal === 'string' && !keyVal.includes(' ')) {
831
+ key = this.generateNode(p.key) + ' =>'
832
+ } else {
833
+ key = this.generateNode(p.key) + ' =>'
834
+ }
835
+ } else {
836
+ key = this.generateNode(p.key) + ' =>'
837
+ }
838
+
839
+ value = this.generateNode(p.value)
840
+ return `${key} ${value}`
841
+ })
842
+ return `{ ${pairs.join(', ')} }`
843
+ }
844
+
845
+ generateRange(node) {
846
+ const start = this.generateNode(node.start)
847
+ const end = this.generateNode(node.end)
848
+ const op = node.exclusive ? '...' : '..'
849
+ return `${start}${op}${end}`
850
+ }
851
+
852
+ generateSymbol(node) {
853
+ return ':' + (node.name || node.value)
854
+ }
855
+
856
+ generateStringInterpolation(node) {
857
+ let result = '"'
858
+ for (const part of node.parts || []) {
859
+ if (part.type === 'Literal' || typeof part === 'string') {
860
+ result += typeof part === 'string' ? part : part.value
861
+ } else if (part.expression) {
862
+ result += '#{' + this.generateNode(part.expression) + '}'
863
+ } else {
864
+ result += '#{' + this.generateNode(part) + '}'
865
+ }
866
+ }
867
+ result += '"'
868
+ return result
869
+ }
870
+
871
+ translateIdentifier(name) {
872
+ if (!name) return ''
873
+
874
+ if (name.startsWith('@')) return name
875
+ if (name.startsWith('@@')) return name
876
+ if (name.startsWith('$')) return name
877
+ if (name.startsWith(':')) return name
878
+
879
+ return this.translate(name)
880
+ }
881
+
882
+ generateLiteral(node) {
883
+ if (node.value === null || node.value === undefined) return 'nil'
884
+ if (node.value === true) return 'true'
885
+ if (node.value === false) return 'false'
886
+ if (typeof node.value === 'string') {
887
+ if (node.symbol) return ':' + node.value
888
+ return `"${node.value.replace(/"/g, '\\"')}"`
889
+ }
890
+ return String(node.value)
891
+ }
892
+
893
+ writeLine(text) {
894
+ this.output += this.getIndent() + text + '\n'
895
+ }
896
+
897
+ getIndent() {
898
+ return ' '.repeat(this.indent)
899
+ }
900
+ }
901
+
902
+ module.exports = {
903
+ RubyGenerator
904
+ }