ether-code 0.9.0 → 0.9.2

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.
@@ -5,361 +5,565 @@ class PHPGenerator {
5
5
  this.i18n = null
6
6
  this.indent = 0
7
7
  this.output = ''
8
- this.translationMap = {}
9
- this.supportedLanguages = ['fr', 'en', 'es', 'ru', 'zh', 'ja']
10
8
 
11
9
  if (i18nPath) {
12
10
  this.loadI18n(i18nPath)
13
11
  }
12
+
13
+ this.buildFunctionMap()
14
14
  }
15
15
 
16
16
  loadI18n(filePath) {
17
17
  const content = fs.readFileSync(filePath, 'utf-8')
18
18
  this.i18n = JSON.parse(content)
19
- this.buildTranslationMap()
20
19
  }
21
20
 
22
- buildTranslationMap() {
23
- this.translationMap = {}
24
-
25
- if (!this.i18n) return
21
+ normalizeAccents(str) {
22
+ if (typeof str !== 'string') return String(str)
23
+ return str.toLowerCase()
24
+ .normalize('NFD')
25
+ .replace(/[\u0300-\u036f]/g, '')
26
+ .replace(/-/g, ' ')
27
+ .replace(/_/g, ' ')
28
+ }
29
+
30
+ buildFunctionMap() {
31
+ this.functionMap = {
32
+ 'est chaine': 'is_string',
33
+ 'est entier': 'is_int',
34
+ 'est flottant': 'is_float',
35
+ 'est tableau': 'is_array',
36
+ 'est booleen': 'is_bool',
37
+ 'est nul': 'is_null',
38
+ 'est null': 'is_null',
39
+ 'est objet': 'is_object',
40
+ 'est numerique': 'is_numeric',
41
+ 'est callable': 'is_callable',
42
+ 'est appelable': 'is_callable',
43
+ 'est ressource': 'is_resource',
44
+ 'est scalaire': 'is_scalar',
45
+ 'est vide': 'empty',
46
+ 'est iterable': 'is_iterable',
47
+ 'est comptable': 'is_countable',
48
+ 'obtenir type': 'gettype',
49
+ 'definir type': 'settype',
50
+ 'valeur entier': 'intval',
51
+ 'valeur entiere': 'intval',
52
+ 'valeur flottant': 'floatval',
53
+ 'valeur chaine': 'strval',
54
+ 'valeur booleen': 'boolval',
55
+ 'longueur': 'strlen',
56
+ 'sous chaine': 'substr',
57
+ 'sous-chaine': 'substr',
58
+ 'position': 'strpos',
59
+ 'remplacer': 'str_replace',
60
+ 'exploser': 'explode',
61
+ 'imploser': 'implode',
62
+ 'majuscules': 'strtoupper',
63
+ 'minuscules': 'strtolower',
64
+ 'couper espaces': 'trim',
65
+ 'rogner': 'trim',
66
+ 'rogner gauche': 'ltrim',
67
+ 'rogner droite': 'rtrim',
68
+ 'repeter': 'str_repeat',
69
+ 'inverser chaine': 'strrev',
70
+ 'remplir': 'str_pad',
71
+ 'remplir gauche': 'str_pad',
72
+ 'compter': 'count',
73
+ 'trier': 'sort',
74
+ 'trier inverse': 'rsort',
75
+ 'trier associatif': 'asort',
76
+ 'trier cles': 'ksort',
77
+ 'inverser': 'array_reverse',
78
+ 'fusionner': 'array_merge',
79
+ 'fusionner tableaux': 'array_merge',
80
+ 'filtrer': 'array_filter',
81
+ 'filtrer tableau': 'array_filter',
82
+ 'mapper': 'array_map',
83
+ 'mapper tableau': 'array_map',
84
+ 'transformer': 'array_map',
85
+ 'reduire': 'array_reduce',
86
+ 'reduire tableau': 'array_reduce',
87
+ 'cles': 'array_keys',
88
+ 'tableau cles': 'array_keys',
89
+ 'valeurs': 'array_values',
90
+ 'tableau valeurs': 'array_values',
91
+ 'pousser': 'array_push',
92
+ 'ajouter': 'array_push',
93
+ 'retirer': 'array_pop',
94
+ 'rechercher': 'array_search',
95
+ 'existe': 'array_key_exists',
96
+ 'cle existe': 'array_key_exists',
97
+ 'dans tableau': 'in_array',
98
+ 'somme': 'array_sum',
99
+ 'somme tableau': 'array_sum',
100
+ 'unique': 'array_unique',
101
+ 'decouper': 'array_slice',
102
+ 'epissure': 'array_splice',
103
+ 'combiner': 'array_combine',
104
+ 'remplir tableau': 'array_fill',
105
+ 'colonne': 'array_column',
106
+ 'compter valeurs': 'array_count_values',
107
+ 'difference': 'array_diff',
108
+ 'intersection': 'array_intersect',
109
+ 'premier': 'reset',
110
+ 'dernier': 'end',
111
+ 'suivant': 'next',
112
+ 'precedent': 'prev',
113
+ 'courant': 'current',
114
+ 'cle courante': 'key',
115
+ 'melanger': 'shuffle',
116
+ 'absolu': 'abs',
117
+ 'plafond': 'ceil',
118
+ 'plancher': 'floor',
119
+ 'arrondir': 'round',
120
+ 'arrondi': 'round',
121
+ 'racine carree': 'sqrt',
122
+ 'puissance': 'pow',
123
+ 'aleatoire': 'rand',
124
+ 'aleatoire entier': 'random_int',
125
+ 'maximum': 'max',
126
+ 'minimum': 'min',
127
+ 'pi': 'pi',
128
+ 'exponentielle': 'exp',
129
+ 'logarithme': 'log',
130
+ 'logarithme10': 'log10',
131
+ 'sinus': 'sin',
132
+ 'cosinus': 'cos',
133
+ 'tangente': 'tan',
134
+ 'formater nombre': 'number_format',
135
+ 'date': 'date',
136
+ 'temps': 'time',
137
+ 'maintenant': 'time',
138
+ 'creer date': 'mktime',
139
+ 'date vers temps': 'strtotime',
140
+ 'formater date': 'date_format',
141
+ 'fichier existe': 'file_exists',
142
+ 'lire fichier': 'file_get_contents',
143
+ 'ecrire fichier': 'file_put_contents',
144
+ 'ouvrir fichier': 'fopen',
145
+ 'fermer fichier': 'fclose',
146
+ 'lire ligne': 'fgets',
147
+ 'ecrire ligne': 'fputs',
148
+ 'est fichier': 'is_file',
149
+ 'est repertoire': 'is_dir',
150
+ 'creer repertoire': 'mkdir',
151
+ 'supprimer fichier': 'unlink',
152
+ 'renommer': 'rename',
153
+ 'copier': 'copy',
154
+ 'taille fichier': 'filesize',
155
+ 'encoder json': 'json_encode',
156
+ 'decoder json': 'json_decode',
157
+ 'json encoder': 'json_encode',
158
+ 'json decoder': 'json_decode',
159
+ 'correspondre': 'preg_match',
160
+ 'regex correspondance': 'preg_match',
161
+ 'regex toutes': 'preg_match_all',
162
+ 'regex remplacer': 'preg_replace',
163
+ 'demarrer session': 'session_start',
164
+ 'detruire session': 'session_destroy',
165
+ 'definir cookie': 'setcookie',
166
+ 'hacher': 'hash',
167
+ 'hacher mot de passe': 'password_hash',
168
+ 'verifier mot de passe': 'password_verify',
169
+ 'md5': 'md5',
170
+ 'sha1': 'sha1',
171
+ 'base64 encoder': 'base64_encode',
172
+ 'base64 decoder': 'base64_decode',
173
+ 'url encoder': 'urlencode',
174
+ 'url decoder': 'urldecode',
175
+ 'htmlentites': 'htmlentities',
176
+ 'html special': 'htmlspecialchars',
177
+ 'enlever balises': 'strip_tags',
178
+ 'echapper': 'addslashes',
179
+ 'preparer': 'prepare',
180
+ 'executer': 'execute',
181
+ 'recuperer': 'fetch',
182
+ 'recuperer tout': 'fetchAll',
183
+ 'nombre lignes': 'rowCount',
184
+ 'dernier id': 'lastInsertId',
185
+ 'afficher info': 'var_dump',
186
+ 'imprimer r': 'print_r',
187
+ 'exporter': 'var_export',
188
+ 'vide': 'empty',
189
+ 'defini': 'isset',
190
+ 'supprimer': 'unset',
191
+ 'sortie': 'exit',
192
+ 'mourir': 'die',
193
+ 'dormir': 'sleep',
194
+ 'micro dormir': 'usleep',
195
+ 'entete': 'header',
196
+ 'en tete': 'header',
197
+ 'rediriger': 'header',
198
+ 'serialiser': 'serialize',
199
+ 'deserialiser': 'unserialize',
200
+ 'formater': 'format',
201
+ 'getmessage': 'getMessage'
202
+ }
26
203
 
27
- for (const [sectionName, section] of Object.entries(this.i18n)) {
28
- if (sectionName === 'metadata' || typeof section !== 'object') continue
29
-
30
- for (const lang of this.supportedLanguages) {
31
- if (!section[lang]) continue
32
-
33
- for (const [etherWord, phpCode] of Object.entries(section[lang])) {
34
- const fixed = this.normalizeUTF8(etherWord)
35
- const normalizedKey = fixed.toLowerCase().trim()
36
- const cleanPhp = this.cleanPhpValue(phpCode)
37
- this.translationMap[normalizedKey] = cleanPhp
38
-
39
- const withoutAccents = this.removeAccents(normalizedKey)
40
- if (withoutAccents !== normalizedKey) {
41
- this.translationMap[withoutAccents] = cleanPhp
42
- }
43
- }
44
- }
204
+ this.keywordMap = {
205
+ 'variable': '',
206
+ 'constante': 'const',
207
+ 'fonction': 'function',
208
+ 'retourner': 'return',
209
+ 'si': 'if',
210
+ 'sinon': 'else',
211
+ 'sinon si': 'elseif',
212
+ 'pour': 'for',
213
+ 'pour chaque': 'foreach',
214
+ 'tant que': 'while',
215
+ 'faire': 'do',
216
+ 'selon': 'switch',
217
+ 'cas': 'case',
218
+ 'defaut': 'default',
219
+ 'sortir': 'break',
220
+ 'continuer': 'continue',
221
+ 'essayer': 'try',
222
+ 'attraper': 'catch',
223
+ 'finalement': 'finally',
224
+ 'lancer': 'throw',
225
+ 'nouveau': 'new',
226
+ 'nouvelle': 'new',
227
+ 'classe': 'class',
228
+ 'interface': 'interface',
229
+ 'trait': 'trait',
230
+ 'etend': 'extends',
231
+ 'etendre': 'extends',
232
+ 'implemente': 'implements',
233
+ 'implementer': 'implements',
234
+ 'utiliser': 'use',
235
+ 'public': 'public',
236
+ 'publique': 'public',
237
+ 'prive': 'private',
238
+ 'privee': 'private',
239
+ 'protege': 'protected',
240
+ 'protegee': 'protected',
241
+ 'statique': 'static',
242
+ 'final': 'final',
243
+ 'finale': 'final',
244
+ 'abstrait': 'abstract',
245
+ 'abstraite': 'abstract',
246
+ 'constructeur': '__construct',
247
+ 'destructeur': '__destruct',
248
+ 'obtenir': '__get',
249
+ 'definir': '__set',
250
+ 'appeler': '__call',
251
+ 'vers chaine': '__toString',
252
+ 'espace de noms': 'namespace',
253
+ 'comme': 'as',
254
+ 'vrai': 'true',
255
+ 'faux': 'false',
256
+ 'nul': 'null',
257
+ 'rien': 'null',
258
+ 'ceci': '$this',
259
+ 'parent': 'parent',
260
+ 'self': 'self',
261
+ 'soi': 'self',
262
+ 'et': '&&',
263
+ 'ou': '||',
264
+ 'non': '!',
265
+ 'egal': '===',
266
+ 'different': '!==',
267
+ 'concatener': '.',
268
+ 'tableau': 'array',
269
+ 'chaine': 'string',
270
+ 'entier': 'int',
271
+ 'flottant': 'float',
272
+ 'booleen': 'bool',
273
+ 'objet': 'object',
274
+ 'vide type': 'void',
275
+ 'void': 'void',
276
+ 'mixte': 'mixed',
277
+ 'afficher': 'echo',
278
+ 'imprimer': 'print',
279
+ 'inclure': 'include',
280
+ 'inclure une fois': 'include_once',
281
+ 'requiert': 'require',
282
+ 'requerir': 'require',
283
+ 'requiert une fois': 'require_once',
284
+ 'requerir une fois': 'require_once',
285
+ 'rendement': 'yield',
286
+ 'rendement de': 'yield from'
45
287
  }
46
288
  }
47
289
 
48
- normalizeUTF8(str) {
49
- if (typeof str !== 'string') return String(str)
50
-
51
- try {
52
- const bytes = new Uint8Array(str.length)
53
- for (let i = 0; i < str.length; i++) {
54
- bytes[i] = str.charCodeAt(i)
55
- }
56
- const decoder = new TextDecoder('utf-8')
57
- return decoder.decode(bytes)
58
- } catch (e) {
59
- return str
60
- }
290
+ translate(word) {
291
+ if (!word) return word
292
+ const normalized = this.normalizeAccents(word)
293
+ return this.functionMap[normalized] || this.keywordMap[normalized] || word
61
294
  }
62
295
 
63
- cleanPhpValue(value) {
64
- if (typeof value !== 'string') return value
65
- let cleaned = value.replace(/\(\)$/, '')
66
- return cleaned
296
+ translateFunction(word) {
297
+ if (!word) return word
298
+ const normalized = this.normalizeAccents(word)
299
+ return this.functionMap[normalized] || word
67
300
  }
68
301
 
69
- translate(word) {
302
+ translateKeyword(word) {
70
303
  if (!word) return word
71
- if (typeof word !== 'string') return String(word)
72
-
73
- const normalized = word.toLowerCase().trim()
74
-
75
- if (this.translationMap[normalized]) {
76
- return this.translationMap[normalized]
77
- }
78
-
79
- const withoutAccents = this.removeAccents(normalized)
80
- if (this.translationMap[withoutAccents]) {
81
- return this.translationMap[withoutAccents]
82
- }
83
-
84
- if (normalized.includes(' ')) {
85
- const words = normalized.split(' ')
86
-
87
- for (let i = words.length - 1; i >= 1; i--) {
88
- const partial = words.slice(0, i + 1).join(' ')
89
- if (this.translationMap[partial]) {
90
- return this.translationMap[partial]
91
- }
92
- const partialNoAccent = this.removeAccents(partial)
93
- if (this.translationMap[partialNoAccent]) {
94
- return this.translationMap[partialNoAccent]
95
- }
96
- }
97
-
98
- const firstWord = words[0]
99
- if (this.translationMap[firstWord]) {
100
- const result = this.translationMap[firstWord]
101
- if (!result.startsWith('__') && !result.startsWith('$')) {
102
- return result
103
- }
104
- }
105
- const firstWordNoAccent = this.removeAccents(firstWord)
106
- if (this.translationMap[firstWordNoAccent]) {
107
- const result = this.translationMap[firstWordNoAccent]
108
- if (!result.startsWith('__') && !result.startsWith('$')) {
109
- return result
110
- }
111
- }
112
- }
113
-
114
- return word
304
+ const normalized = this.normalizeAccents(word)
305
+ return this.keywordMap[normalized] || word
115
306
  }
116
307
 
117
- removeAccents(str) {
118
- if (typeof str !== 'string') return String(str)
119
- return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
308
+ extractTypeName(typeNode) {
309
+ if (!typeNode) return ''
310
+ if (typeof typeNode === 'string') return this.translateKeyword(typeNode)
311
+ if (typeNode.name) return this.translateKeyword(typeNode.name)
312
+ if (typeNode.type === 'Identifier') return this.translateKeyword(typeNode.name || typeNode.value || '')
313
+ if (typeNode.value) return this.translateKeyword(typeNode.value)
314
+ return ''
120
315
  }
121
316
 
122
317
  generate(ast) {
123
- this.output = '<?php\n'
318
+ this.output = ''
124
319
  this.indent = 0
125
-
126
- if (ast.strict) {
127
- this.writeLine("declare(strict_types=1);")
128
- this.writeLine('')
320
+
321
+ if (ast.phpTag !== false) {
322
+ this.output = '<?php\n'
129
323
  }
130
324
 
131
- if (Array.isArray(ast.body)) {
132
- for (const node of ast.body) {
325
+ if (Array.isArray(ast)) {
326
+ for (const node of ast) {
133
327
  this.generateNode(node)
134
328
  }
135
- } else if (ast.body) {
136
- this.generateNode(ast.body)
137
- } else {
329
+ } else if (ast && ast.type === 'Program') {
330
+ for (const node of ast.body || []) {
331
+ this.generateNode(node)
332
+ }
333
+ } else if (ast && ast.type) {
138
334
  this.generateNode(ast)
335
+ } else if (ast && ast.body) {
336
+ for (const node of ast.body) {
337
+ this.generateNode(node)
338
+ }
139
339
  }
140
340
 
141
- return this.output
341
+ return this.output.trim()
142
342
  }
143
343
 
144
344
  generateNode(node) {
145
345
  if (!node) return ''
146
346
 
147
- if (Array.isArray(node)) {
148
- return node.map(n => this.generateNode(n)).join('')
347
+ switch (node.type) {
348
+ case 'Program':
349
+ for (const stmt of node.body || []) {
350
+ this.generateNode(stmt)
351
+ }
352
+ return ''
353
+ case 'DeclareStatement':
354
+ return this.generateDeclareStatement(node)
355
+ case 'NamespaceDeclaration':
356
+ return this.generateNamespace(node)
357
+ case 'UseStatement':
358
+ return this.generateUseStatement(node)
359
+ case 'ConstantDeclaration':
360
+ return this.generateConstantDeclaration(node)
361
+ case 'VariableDeclaration':
362
+ return this.generateVariableDeclaration(node)
363
+ case 'FunctionDeclaration':
364
+ return this.generateFunctionDeclaration(node)
365
+ case 'ClassDeclaration':
366
+ return this.generateClassDeclaration(node)
367
+ case 'InterfaceDeclaration':
368
+ return this.generateInterfaceDeclaration(node)
369
+ case 'TraitDeclaration':
370
+ return this.generateTraitDeclaration(node)
371
+ case 'EnumDeclaration':
372
+ return this.generateEnumDeclaration(node)
373
+ case 'IfStatement':
374
+ return this.generateIfStatement(node)
375
+ case 'ForStatement':
376
+ return this.generateForStatement(node)
377
+ case 'ForEachStatement':
378
+ return this.generateForEachStatement(node)
379
+ case 'WhileStatement':
380
+ return this.generateWhileStatement(node)
381
+ case 'DoWhileStatement':
382
+ return this.generateDoWhileStatement(node)
383
+ case 'SwitchStatement':
384
+ return this.generateSwitchStatement(node)
385
+ case 'TryStatement':
386
+ return this.generateTryStatement(node)
387
+ case 'ReturnStatement':
388
+ return this.generateReturnStatement(node)
389
+ case 'ThrowStatement':
390
+ return this.generateThrowStatement(node)
391
+ case 'BreakStatement':
392
+ this.writeLine('break;')
393
+ return ''
394
+ case 'ContinueStatement':
395
+ this.writeLine('continue;')
396
+ return ''
397
+ case 'ExpressionStatement':
398
+ return this.generateExpressionStatement(node)
399
+ case 'EchoStatement':
400
+ return this.generateEchoStatement(node)
401
+ case 'BlockStatement':
402
+ return this.generateBlockStatement(node)
403
+ case 'CallExpression':
404
+ return this.generateCallExpression(node)
405
+ case 'MemberExpression':
406
+ return this.generateMemberExpression(node)
407
+ case 'StaticMemberExpression':
408
+ return this.generateStaticMemberExpression(node)
409
+ case 'BinaryExpression':
410
+ return this.generateBinaryExpression(node)
411
+ case 'LogicalExpression':
412
+ return this.generateBinaryExpression(node)
413
+ case 'AssignmentExpression':
414
+ return this.generateAssignmentExpression(node)
415
+ case 'UnaryExpression':
416
+ return this.generateUnaryExpression(node)
417
+ case 'UpdateExpression':
418
+ return this.generateUpdateExpression(node)
419
+ case 'ConditionalExpression':
420
+ return this.generateConditionalExpression(node)
421
+ case 'ArrayExpression':
422
+ return this.generateArrayExpression(node)
423
+ case 'ObjectExpression':
424
+ return this.generateObjectExpression(node)
425
+ case 'Identifier':
426
+ return this.generateIdentifier(node)
427
+ case 'Variable':
428
+ return this.generateVariable(node)
429
+ case 'Literal':
430
+ return this.generateLiteral(node)
431
+ case 'StringLiteral':
432
+ return this.generateStringLiteral(node)
433
+ case 'NewExpression':
434
+ return this.generateNewExpression(node)
435
+ case 'ThisExpression':
436
+ return '$this'
437
+ case 'FunctionExpression':
438
+ return this.generateFunctionExpression(node)
439
+ case 'ArrowFunctionExpression':
440
+ return this.generateArrowFunctionExpression(node)
441
+ case 'IndexExpression':
442
+ return this.generateIndexExpression(node)
443
+ case 'NullCoalesceExpression':
444
+ return this.generateNullCoalesceExpression(node)
445
+ case 'YieldExpression':
446
+ return this.generateYieldExpression(node)
447
+ default:
448
+ return ''
149
449
  }
450
+ }
150
451
 
151
- const type = node.type || node.nodeType
152
-
153
- const generators = {
154
- 'Program': () => this.generateProgram(node),
155
- 'DeclareStatement': () => this.generateDeclareStatement(node),
156
- 'NamespaceDeclaration': () => this.generateNamespaceDeclaration(node),
157
- 'FunctionDeclaration': () => this.generateFunctionDeclaration(node),
158
- 'ClassDeclaration': () => this.generateClassDeclaration(node),
159
- 'InterfaceDeclaration': () => this.generateInterfaceDeclaration(node),
160
- 'TraitDeclaration': () => this.generateTraitDeclaration(node),
161
- 'EnumDeclaration': () => this.generateEnumDeclaration(node),
162
- 'MethodDefinition': () => this.generateMethodDefinition(node),
163
- 'PropertyDeclaration': () => this.generatePropertyDeclaration(node),
164
- 'VariableDeclaration': () => this.generateVariableDeclaration(node),
165
- 'ConstantDeclaration': () => this.generateConstantDeclaration(node),
166
- 'DefineDeclaration': () => this.generateDefineDeclaration(node),
167
- 'IfStatement': () => this.generateIfStatement(node),
168
- 'ForStatement': () => this.generateForStatement(node),
169
- 'ForEachStatement': () => this.generateForEachStatement(node),
170
- 'ForeachStatement': () => this.generateForEachStatement(node),
171
- 'WhileStatement': () => this.generateWhileStatement(node),
172
- 'DoWhileStatement': () => this.generateDoWhileStatement(node),
173
- 'SwitchStatement': () => this.generateSwitchStatement(node),
174
- 'MatchExpression': () => this.generateMatchExpression(node),
175
- 'TryStatement': () => this.generateTryStatement(node),
176
- 'ReturnStatement': () => this.generateReturnStatement(node),
177
- 'ThrowStatement': () => this.generateThrowStatement(node),
178
- 'BreakStatement': () => this.writeLine('break;'),
179
- 'ContinueStatement': () => this.writeLine('continue;'),
180
- 'ExpressionStatement': () => this.generateExpressionStatement(node),
181
- 'EchoStatement': () => this.generateEchoStatement(node),
182
- 'PrintStatement': () => this.generatePrintStatement(node),
183
- 'Namespace': () => this.generateNamespace(node),
184
- 'UseStatement': () => this.generateUseStatement(node),
185
- 'IncludeStatement': () => this.generateIncludeStatement(node),
186
- 'Include': () => this.generateInclude(node),
187
- 'CallExpression': () => this.generateCallExpression(node),
188
- 'MemberExpression': () => this.generateMemberExpression(node),
189
- 'BinaryExpression': () => this.generateBinaryExpression(node),
190
- 'LogicalExpression': () => this.generateBinaryExpression(node),
191
- 'UnaryExpression': () => this.generateUnaryExpression(node),
192
- 'AssignmentExpression': () => this.generateAssignmentExpression(node),
193
- 'UpdateExpression': () => this.generateUpdateExpression(node),
194
- 'ConditionalExpression': () => this.generateConditionalExpression(node),
195
- 'NullCoalescing': () => this.generateNullCoalescing(node),
196
- 'ArrayExpression': () => this.generateArrayExpression(node),
197
- 'ObjectExpression': () => this.generateObjectExpression(node),
198
- 'ArrowFunction': () => this.generateArrowFunction(node),
199
- 'ArrowFunctionExpression': () => this.generateArrowFunction(node),
200
- 'Closure': () => this.generateClosure(node),
201
- 'FunctionExpression': () => this.generateClosure(node),
202
- 'Identifier': () => this.generateIdentifier(node),
203
- 'Variable': () => this.generateVariable(node),
204
- 'Literal': () => this.generateLiteral(node),
205
- 'StringLiteral': () => this.generateStringLiteral(node),
206
- 'NewExpression': () => this.generateNewExpression(node),
207
- 'InstanceOf': () => this.generateInstanceOf(node),
208
- 'BlockStatement': () => this.generateBlockStatement(node),
209
- 'ThisExpression': () => '$this',
210
- 'StaticMemberExpression': () => this.generateStaticMemberExpression(node),
211
- 'IndexExpression': () => this.generateIndexExpression(node),
212
- 'YieldExpression': () => this.generateYieldExpression(node),
213
- 'EmptyStatement': () => '',
214
- 'Comment': () => ''
452
+ generateDeclareStatement(node) {
453
+ const directives = []
454
+ if (node.strictTypes) {
455
+ directives.push('strict_types=1')
215
456
  }
216
-
217
- if (generators[type]) {
218
- return generators[type]()
457
+ if (node.ticks) {
458
+ directives.push(`ticks=${node.ticks}`)
219
459
  }
220
-
221
- if (node.expression) {
222
- return this.generateNode(node.expression)
460
+ if (node.encoding) {
461
+ directives.push(`encoding='${node.encoding}'`)
462
+ }
463
+ if (directives.length > 0) {
464
+ this.writeLine(`declare(${directives.join(', ')});`)
223
465
  }
466
+ return ''
467
+ }
224
468
 
469
+ generateNamespace(node) {
470
+ const name = node.name || node.path
471
+ this.writeLine(`namespace ${name};`)
472
+ this.writeLine('')
225
473
  return ''
226
474
  }
227
475
 
228
- generateProgram(node) {
229
- for (const stmt of node.body || []) {
230
- this.generateNode(stmt)
231
- }
476
+ generateUseStatement(node) {
477
+ const name = node.name || node.source
478
+ const alias = node.alias ? ` as ${node.alias}` : ''
479
+ this.writeLine(`use ${name}${alias};`)
480
+ return ''
232
481
  }
233
482
 
234
- generateDeclareStatement(node) {
235
- const directives = (node.directives || []).map(d => {
236
- const name = this.safeString(d.name)
237
- const value = this.generateNode(d.value)
238
- return `${name}=${value}`
239
- }).join(', ')
240
-
241
- this.writeLine(`declare(${directives});`)
242
- this.writeLine('')
483
+ generateConstantDeclaration(node) {
484
+ const name = node.name || node.id?.name
485
+ const value = this.generateNode(node.value || node.init)
486
+ this.writeLine(`define('${name}', ${value});`)
487
+ return ''
243
488
  }
244
489
 
245
- generateNamespaceDeclaration(node) {
246
- const name = this.safeString(node.name)
247
- this.writeLine(`namespace ${name};`)
248
- this.writeLine('')
490
+ generateVariableDeclaration(node) {
491
+ const declarations = node.declarations || [node]
249
492
 
250
- if (node.body) {
251
- this.generateNode(node.body)
493
+ for (const decl of declarations) {
494
+ const name = this.generateVariable(decl.id || decl)
495
+ if (decl.init) {
496
+ const init = this.generateNode(decl.init)
497
+ this.writeLine(`${name} = ${init};`)
498
+ }
252
499
  }
253
- }
254
-
255
- generateDefineDeclaration(node) {
256
- const name = this.safeString(node.name)
257
- const value = this.generateNode(node.value)
258
- this.writeLine(`define('${name}', ${value});`)
500
+ return ''
259
501
  }
260
502
 
261
503
  generateFunctionDeclaration(node) {
262
- const name = this.safeString(node.name || node.id?.name || 'anonymous')
263
- const translatedName = this.translate(name)
264
- const params = this.generateParams(node.params || [])
265
- const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
504
+ const name = node.name || node.id?.name || ''
505
+ const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
506
+ let returnType = ''
266
507
 
267
- this.writeLine(`function ${translatedName}(${params})${returnType} {`)
508
+ if (node.returnType) {
509
+ const type = this.extractTypeName(node.returnType)
510
+ if (type) returnType = ': ' + type
511
+ }
512
+
513
+ this.writeLine(`function ${name}(${params})${returnType} {`)
268
514
  this.indent++
269
- this.generateNode(node.body)
515
+
516
+ if (node.body) {
517
+ this.generateBody(node.body)
518
+ }
519
+
270
520
  this.indent--
271
521
  this.writeLine('}')
272
522
  this.writeLine('')
523
+ return ''
273
524
  }
274
525
 
275
- generateParams(params) {
276
- return params.map(p => {
277
- let result = ''
278
-
279
- if (p.visibility) {
280
- result += this.translate(p.visibility) + ' '
281
- }
282
-
283
- if (p.readonly) {
284
- result += 'readonly '
285
- }
286
-
287
- const typeHint = p.typeHint || (p.type !== 'Parameter' ? p.type : null)
288
- if (typeHint && typeHint !== 'Parameter') {
289
- result += this.translateType(typeHint) + ' '
290
- }
291
-
292
- if (p.variadic) {
293
- result += '...'
294
- }
295
-
296
- if (p.byReference || p.reference) {
297
- result += '&'
298
- }
299
-
300
- const name = this.safeString(p.name || p.id?.name || p)
301
- result += '$' + name
302
-
303
- if (p.default !== undefined && p.default !== null) {
304
- result += ' = ' + this.generateNode(p.default)
305
- }
306
-
307
- return result
308
- }).join(', ')
309
- }
310
-
311
- safeString(value) {
312
- if (typeof value === 'string') return value
313
- if (value && typeof value === 'object') {
314
- return value.name || value.value || 'unknown'
315
- }
316
- return String(value || 'unknown')
317
- }
318
-
319
- translateType(type) {
320
- if (!type) return ''
526
+ generateParam(param) {
527
+ let result = ''
321
528
 
322
- if (typeof type === 'string') {
323
- const typeMap = {
324
- 'chaîne': 'string', 'chaine': 'string', 'string': 'string',
325
- 'entier': 'int', 'integer': 'int', 'int': 'int',
326
- 'flottant': 'float', 'float': 'float',
327
- 'booléen': 'bool', 'booleen': 'bool', 'boolean': 'bool', 'bool': 'bool',
328
- 'tableau': 'array', 'array': 'array',
329
- 'objet': 'object', 'object': 'object',
330
- 'nul': 'null', 'null': 'null',
331
- 'vide': 'void', 'void': 'void',
332
- 'mixte': 'mixed', 'mixed': 'mixed',
333
- 'appelable': 'callable', 'callable': 'callable',
334
- 'itérable': 'iterable', 'iterable': 'iterable',
335
- 'jamais': 'never', 'never': 'never'
529
+ if (param.typeHint || (param.type && param.type !== 'Parameter' && param.type !== 'Identifier')) {
530
+ const type = param.typeHint || param.type
531
+ if (typeof type === 'string' && type !== 'Parameter' && type !== 'Identifier') {
532
+ result += this.translateKeyword(type) + ' '
533
+ } else if (type && type.name) {
534
+ result += this.translateKeyword(type.name) + ' '
336
535
  }
337
- const lower = type.toLowerCase()
338
- return typeMap[lower] || type
339
536
  }
340
537
 
341
- if (type.type === 'TypeHint' && type.types) {
342
- const typeStr = type.types.join('|')
343
- return (type.nullable ? '?' : '') + typeStr
538
+ if (param.reference) {
539
+ result += '&'
344
540
  }
345
541
 
346
- if (type.union && type.types) {
347
- return type.types.map(t => this.translateType(t)).join('|')
542
+ if (param.variadic) {
543
+ result += '...'
348
544
  }
349
545
 
350
- if (type.intersection && type.types) {
351
- return type.types.map(t => this.translateType(t)).join('&')
352
- }
546
+ result += this.generateVariable(param)
353
547
 
354
- if (type.nullable && type.type) {
355
- return '?' + this.translateType(type.type)
548
+ if (param.default !== undefined && param.default !== null) {
549
+ result += ' = ' + this.generateNode(param.default)
356
550
  }
357
551
 
358
- if (type.name) {
359
- return this.translateType(type.name)
552
+ return result
553
+ }
554
+
555
+ generateBody(body) {
556
+ if (Array.isArray(body)) {
557
+ for (const stmt of body) {
558
+ this.generateNode(stmt)
559
+ }
560
+ } else if (body && body.body) {
561
+ for (const stmt of body.body) {
562
+ this.generateNode(stmt)
563
+ }
564
+ } else if (body) {
565
+ this.generateNode(body)
360
566
  }
361
-
362
- return String(type)
363
567
  }
364
568
 
365
569
  generateClassDeclaration(node) {
@@ -369,237 +573,210 @@ class PHPGenerator {
369
573
  if (node.final) declaration += 'final '
370
574
  if (node.readonly) declaration += 'readonly '
371
575
 
372
- const className = this.safeString(node.name || node.id?.name)
373
- declaration += 'class ' + this.translate(className)
374
-
375
- if (node.extends) {
376
- declaration += ' extends ' + this.translate(this.safeString(node.extends))
576
+ declaration += 'class ' + (node.name || node.id?.name || '')
577
+
578
+ if (node.extends || node.superClass) {
579
+ declaration += ' extends ' + (node.extends || node.superClass)
377
580
  }
378
-
581
+
379
582
  if (node.implements && node.implements.length > 0) {
380
- declaration += ' implements ' + node.implements.map(i => this.translate(this.safeString(i))).join(', ')
583
+ declaration += ' implements ' + node.implements.join(', ')
381
584
  }
382
-
585
+
383
586
  this.writeLine(declaration + ' {')
384
587
  this.indent++
385
-
386
- for (const member of node.body || node.members || []) {
387
- this.generateNode(member)
588
+
589
+ const members = node.body?.body || node.members || node.body || []
590
+ for (const member of members) {
591
+ this.generateClassMember(member)
388
592
  }
389
-
593
+
390
594
  this.indent--
391
595
  this.writeLine('}')
392
596
  this.writeLine('')
597
+ return ''
393
598
  }
394
599
 
395
- generateInterfaceDeclaration(node) {
396
- const name = this.safeString(node.name || node.id?.name)
397
- let declaration = 'interface ' + this.translate(name)
600
+ generateClassMember(node) {
601
+ if (!node) return
602
+
603
+ if (node.type === 'UseTraitStatement') {
604
+ this.writeLine(`use ${node.traits.join(', ')};`)
605
+ return
606
+ }
607
+
608
+ const visibility = node.visibility || 'public'
609
+ const isStatic = node.static ? 'static ' : ''
610
+ const isReadonly = node.readonly ? 'readonly ' : ''
398
611
 
612
+ if (node.type === 'Property' || node.kind === 'property' || node.type === 'PropertyDeclaration') {
613
+ const name = '$' + (node.name || node.key?.name || node.id?.name || '')
614
+ let typeHint = ''
615
+ if (node.typeHint) {
616
+ typeHint = this.translateKeyword(node.typeHint) + ' '
617
+ }
618
+ const value = node.value ? ' = ' + this.generateNode(node.value) : ''
619
+ this.writeLine(`${visibility} ${isStatic}${isReadonly}${typeHint}${name}${value};`)
620
+ } else if (node.type === 'Method' || node.kind === 'method' || node.type === 'MethodDeclaration') {
621
+ const name = node.name || node.key?.name || ''
622
+ const translatedName = this.translateKeyword(name)
623
+ const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
624
+ let returnType = ''
625
+
626
+ if (node.returnType) {
627
+ const type = this.extractTypeName(node.returnType)
628
+ if (type) returnType = ': ' + type
629
+ }
630
+
631
+ const abstractMod = node.abstract ? 'abstract ' : ''
632
+
633
+ if (node.abstract) {
634
+ this.writeLine(`${abstractMod}${visibility} ${isStatic}function ${translatedName}(${params})${returnType};`)
635
+ } else {
636
+ this.writeLine(`${visibility} ${isStatic}function ${translatedName}(${params})${returnType} {`)
637
+ this.indent++
638
+
639
+ if (node.body) {
640
+ this.generateBody(node.body)
641
+ }
642
+
643
+ this.indent--
644
+ this.writeLine('}')
645
+ }
646
+ this.writeLine('')
647
+ }
648
+ }
649
+
650
+ generateInterfaceDeclaration(node) {
651
+ let declaration = 'interface ' + (node.name || '')
652
+
399
653
  if (node.extends && node.extends.length > 0) {
400
- declaration += ' extends ' + node.extends.map(e => this.translate(this.safeString(e))).join(', ')
654
+ declaration += ' extends ' + node.extends.join(', ')
401
655
  }
402
-
656
+
403
657
  this.writeLine(declaration + ' {')
404
658
  this.indent++
405
-
406
- for (const member of node.body || node.members || []) {
407
- this.generateNode(member)
659
+
660
+ const members = node.body?.body || node.members || node.body || []
661
+ for (const member of members) {
662
+ this.generateInterfaceMember(member)
408
663
  }
409
-
664
+
410
665
  this.indent--
411
666
  this.writeLine('}')
412
667
  this.writeLine('')
668
+ return ''
413
669
  }
414
670
 
415
- generateTraitDeclaration(node) {
416
- const name = this.safeString(node.name || node.id?.name)
417
- this.writeLine('trait ' + this.translate(name) + ' {')
418
- this.indent++
671
+ generateInterfaceMember(node) {
672
+ if (!node) return
673
+
674
+ const visibility = 'public'
675
+ const name = node.name || node.key?.name || ''
676
+ const translatedName = this.translateKeyword(name)
677
+ const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
678
+ let returnType = ''
419
679
 
420
- for (const member of node.body || node.members || []) {
421
- this.generateNode(member)
680
+ if (node.returnType) {
681
+ const type = this.extractTypeName(node.returnType)
682
+ if (type) returnType = ': ' + type
422
683
  }
423
684
 
424
- this.indent--
425
- this.writeLine('}')
685
+ this.writeLine(`${visibility} function ${translatedName}(${params})${returnType};`)
426
686
  this.writeLine('')
427
687
  }
428
688
 
429
- generateEnumDeclaration(node) {
430
- const name = this.safeString(node.name || node.id?.name)
431
- let declaration = 'enum ' + this.translate(name)
432
-
433
- if (node.backingType) {
434
- declaration += ': ' + this.translateType(node.backingType)
435
- }
436
-
437
- this.writeLine(declaration + ' {')
689
+ generateTraitDeclaration(node) {
690
+ this.writeLine('trait ' + (node.name || '') + ' {')
438
691
  this.indent++
439
-
440
- for (const member of node.cases || node.members || []) {
441
- if (member.value !== undefined) {
442
- this.writeLine(`case ${member.name} = ${this.generateNode(member.value)};`)
443
- } else {
444
- this.writeLine(`case ${member.name};`)
445
- }
446
- }
447
-
448
- for (const method of node.methods || []) {
449
- this.generateNode(method)
692
+
693
+ const members = node.body?.body || node.members || node.body || []
694
+ for (const member of members) {
695
+ this.generateClassMember(member)
450
696
  }
451
-
697
+
452
698
  this.indent--
453
699
  this.writeLine('}')
454
700
  this.writeLine('')
701
+ return ''
455
702
  }
456
703
 
457
- generateMethodDefinition(node) {
458
- let declaration = ''
459
-
460
- const visibility = node.visibility || node.access || 'public'
461
- declaration += this.translate(visibility) + ' '
462
-
463
- if (node.static) declaration += 'static '
464
- if (node.final) declaration += 'final '
465
- if (node.abstract) declaration += 'abstract '
466
-
467
- const name = this.translateMethodName(this.safeString(node.name || node.key?.name))
468
- const params = this.generateParams(node.params || node.value?.params || [])
469
- const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
470
-
471
- if (node.abstract) {
472
- this.writeLine(`${declaration}function ${name}(${params})${returnType};`)
473
- } else {
474
- this.writeLine(`${declaration}function ${name}(${params})${returnType} {`)
475
- this.indent++
476
- this.generateNode(node.body || node.value?.body)
477
- this.indent--
478
- this.writeLine('}')
479
- }
480
- this.writeLine('')
481
- }
482
-
483
- translateMethodName(name) {
484
- if (!name) return 'method'
704
+ generateEnumDeclaration(node) {
705
+ let declaration = 'enum ' + (node.name || '')
485
706
 
486
- const magicMethods = {
487
- 'constructeur': '__construct',
488
- 'constructor': '__construct',
489
- 'destructeur': '__destruct',
490
- 'destructor': '__destruct',
491
- 'obtenir': '__get',
492
- 'get': '__get',
493
- 'définir': '__set',
494
- 'definir': '__set',
495
- 'set': '__set',
496
- 'appeler': '__call',
497
- 'call': '__call',
498
- 'appeler statique': '__callStatic',
499
- 'call static': '__callStatic',
500
- 'vers chaîne': '__toString',
501
- 'vers chaine': '__toString',
502
- 'to string': '__toString',
503
- 'invoquer': '__invoke',
504
- 'invoke': '__invoke',
505
- 'cloner': '__clone',
506
- 'clone': '__clone',
507
- 'dormir': '__sleep',
508
- 'sleep': '__sleep',
509
- 'réveiller': '__wakeup',
510
- 'reveiller': '__wakeup',
511
- 'wakeup': '__wakeup',
512
- 'sérialiser': '__serialize',
513
- 'serialiser': '__serialize',
514
- 'serialize': '__serialize',
515
- 'désérialiser': '__unserialize',
516
- 'deserialiser': '__unserialize',
517
- 'unserialize': '__unserialize',
518
- 'isset': '__isset',
519
- 'existe propriété': '__isset',
520
- 'existe propriete': '__isset',
521
- 'unset': '__unset',
522
- 'détruire propriété': '__unset',
523
- 'detruire propriete': '__unset',
524
- 'déboguer info': '__debugInfo',
525
- 'deboguer info': '__debugInfo',
526
- 'debug info': '__debugInfo'
707
+ if (node.backingType) {
708
+ declaration += ': ' + this.translateKeyword(node.backingType)
527
709
  }
528
-
529
- const normalized = name.toLowerCase().trim()
530
- return magicMethods[normalized] || this.translate(name)
531
- }
532
710
 
533
- generatePropertyDeclaration(node) {
534
- let declaration = ''
535
-
536
- const visibility = node.visibility || node.access || 'public'
537
- declaration += this.translate(visibility) + ' '
538
-
539
- if (node.static) declaration += 'static '
540
- if (node.readonly) declaration += 'readonly '
541
-
542
- if (node.type) {
543
- declaration += this.translateType(node.type) + ' '
544
- }
545
-
546
- const name = this.safeString(node.name || node.key?.name)
547
- declaration += '$' + this.translate(name)
548
-
549
- if (node.value !== undefined) {
550
- declaration += ' = ' + this.generateNode(node.value)
551
- }
552
-
553
- this.writeLine(declaration + ';')
554
- }
711
+ this.writeLine(declaration + ' {')
712
+ this.indent++
555
713
 
556
- generateVariableDeclaration(node) {
557
- for (const decl of node.declarations || [node]) {
558
- const name = this.safeString(decl.name || decl.id?.name || decl.id)
559
- const varName = '$' + name
560
-
561
- if (decl.init !== undefined) {
562
- const value = this.generateNode(decl.init)
563
- this.writeLine(`${varName} = ${value};`)
714
+ const cases = node.cases || node.members || []
715
+ for (const caseNode of cases) {
716
+ if (caseNode.value !== undefined) {
717
+ this.writeLine(`case ${caseNode.name} = ${this.generateNode(caseNode.value)};`)
564
718
  } else {
565
- this.writeLine(`${varName} = null;`)
719
+ this.writeLine(`case ${caseNode.name};`)
566
720
  }
567
721
  }
568
- }
569
722
 
570
- generateConstantDeclaration(node) {
571
- const name = this.safeString(node.name || node.id?.name)
572
- const value = this.generateNode(node.value || node.init)
573
-
574
- if (node.classLevel) {
575
- const visibility = node.visibility ? this.translate(node.visibility) + ' ' : ''
576
- this.writeLine(`${visibility}const ${name.toUpperCase()} = ${value};`)
577
- } else {
578
- this.writeLine(`define('${name.toUpperCase()}', ${value});`)
579
- }
723
+ this.indent--
724
+ this.writeLine('}')
725
+ this.writeLine('')
726
+ return ''
580
727
  }
581
728
 
582
729
  generateIfStatement(node) {
583
- const test = this.generateNode(node.test || node.condition)
730
+ const test = this.generateNode(node.test)
584
731
  this.writeLine(`if (${test}) {`)
585
732
  this.indent++
586
- this.generateNode(node.consequent)
733
+ this.generateBody(node.consequent)
587
734
  this.indent--
588
-
735
+
589
736
  if (node.alternate) {
590
737
  if (node.alternate.type === 'IfStatement') {
591
- this.write('} else ')
592
- this.generateIfStatement({ ...node.alternate, isElseIf: true })
738
+ const altTest = this.generateNode(node.alternate.test)
739
+ this.writeLine(`} elseif (${altTest}) {`)
740
+ this.indent++
741
+ this.generateBody(node.alternate.consequent)
742
+ this.indent--
743
+ if (node.alternate.alternate) {
744
+ this.generateElse(node.alternate.alternate)
745
+ } else {
746
+ this.writeLine('}')
747
+ }
593
748
  } else {
594
749
  this.writeLine('} else {')
595
750
  this.indent++
596
- this.generateNode(node.alternate)
751
+ this.generateBody(node.alternate)
597
752
  this.indent--
598
753
  this.writeLine('}')
599
754
  }
600
755
  } else {
601
756
  this.writeLine('}')
602
757
  }
758
+ return ''
759
+ }
760
+
761
+ generateElse(node) {
762
+ if (node.type === 'IfStatement') {
763
+ const altTest = this.generateNode(node.test)
764
+ this.writeLine(`} elseif (${altTest}) {`)
765
+ this.indent++
766
+ this.generateBody(node.consequent)
767
+ this.indent--
768
+ if (node.alternate) {
769
+ this.generateElse(node.alternate)
770
+ } else {
771
+ this.writeLine('}')
772
+ }
773
+ } else {
774
+ this.writeLine('} else {')
775
+ this.indent++
776
+ this.generateBody(node)
777
+ this.indent--
778
+ this.writeLine('}')
779
+ }
603
780
  }
604
781
 
605
782
  generateForStatement(node) {
@@ -609,39 +786,43 @@ class PHPGenerator {
609
786
 
610
787
  this.writeLine(`for (${init}; ${test}; ${update}) {`)
611
788
  this.indent++
612
- this.generateNode(node.body)
789
+ this.generateBody(node.body)
613
790
  this.indent--
614
791
  this.writeLine('}')
792
+ return ''
615
793
  }
616
794
 
617
795
  generateForEachStatement(node) {
618
- const array = this.generateNode(node.array || node.right || node.iterable)
619
- const value = this.generateVariable(node.value || node.left || node.valueVar)
620
- const key = node.key || node.keyVar ? this.generateVariable(node.key || node.keyVar) + ' => ' : ''
796
+ const array = this.generateNode(node.array || node.right)
797
+ const value = this.generateVariable(node.value || node.left)
798
+ const key = node.key ? this.generateVariable(node.key) + ' => ' : ''
621
799
 
622
800
  this.writeLine(`foreach (${array} as ${key}${value}) {`)
623
801
  this.indent++
624
- this.generateNode(node.body)
802
+ this.generateBody(node.body)
625
803
  this.indent--
626
804
  this.writeLine('}')
805
+ return ''
627
806
  }
628
807
 
629
808
  generateWhileStatement(node) {
630
- const test = this.generateNode(node.test || node.condition)
809
+ const test = this.generateNode(node.test)
631
810
  this.writeLine(`while (${test}) {`)
632
811
  this.indent++
633
- this.generateNode(node.body)
812
+ this.generateBody(node.body)
634
813
  this.indent--
635
814
  this.writeLine('}')
815
+ return ''
636
816
  }
637
817
 
638
818
  generateDoWhileStatement(node) {
639
819
  this.writeLine('do {')
640
820
  this.indent++
641
- this.generateNode(node.body)
821
+ this.generateBody(node.body)
642
822
  this.indent--
643
- const test = this.generateNode(node.test || node.condition)
823
+ const test = this.generateNode(node.test)
644
824
  this.writeLine(`} while (${test});`)
825
+ return ''
645
826
  }
646
827
 
647
828
  generateSwitchStatement(node) {
@@ -665,50 +846,35 @@ class PHPGenerator {
665
846
 
666
847
  this.indent--
667
848
  this.writeLine('}')
668
- }
669
-
670
- generateMatchExpression(node) {
671
- const subject = this.generateNode(node.subject || node.discriminant)
672
- let result = `match (${subject}) {\n`
673
-
674
- for (const arm of node.arms || node.cases || []) {
675
- this.indent++
676
- if (arm.conditions) {
677
- const conditions = arm.conditions.map(c => this.generateNode(c)).join(', ')
678
- result += this.getIndent() + `${conditions} => ${this.generateNode(arm.body)},\n`
679
- } else if (arm.default) {
680
- result += this.getIndent() + `default => ${this.generateNode(arm.body)},\n`
681
- }
682
- this.indent--
683
- }
684
-
685
- result += this.getIndent() + '}'
686
- return result
849
+ return ''
687
850
  }
688
851
 
689
852
  generateTryStatement(node) {
690
853
  this.writeLine('try {')
691
854
  this.indent++
692
- this.generateNode(node.block)
855
+ this.generateBody(node.block)
693
856
  this.indent--
694
857
 
695
- for (const handler of node.handlers || (node.handler ? [node.handler] : [])) {
696
- const exceptionType = handler.type || handler.param?.type || 'Exception'
697
- const param = handler.param ? this.generateVariable(handler.param) : '$e'
858
+ if (node.handler) {
859
+ const exceptionType = node.handler.exceptionType || node.handler.type || 'Exception'
860
+ const param = node.handler.param
861
+ ? this.generateVariable(node.handler.param)
862
+ : '$e'
698
863
  this.writeLine(`} catch (${exceptionType} ${param}) {`)
699
864
  this.indent++
700
- this.generateNode(handler.body)
865
+ this.generateBody(node.handler.body)
701
866
  this.indent--
702
867
  }
703
868
 
704
869
  if (node.finalizer) {
705
870
  this.writeLine('} finally {')
706
871
  this.indent++
707
- this.generateNode(node.finalizer)
872
+ this.generateBody(node.finalizer)
708
873
  this.indent--
709
874
  }
710
875
 
711
876
  this.writeLine('}')
877
+ return ''
712
878
  }
713
879
 
714
880
  generateReturnStatement(node) {
@@ -718,93 +884,50 @@ class PHPGenerator {
718
884
  } else {
719
885
  this.writeLine('return;')
720
886
  }
887
+ return ''
721
888
  }
722
889
 
723
890
  generateThrowStatement(node) {
724
891
  const arg = this.generateNode(node.argument)
725
892
  this.writeLine(`throw ${arg};`)
893
+ return ''
726
894
  }
727
895
 
728
896
  generateExpressionStatement(node) {
729
897
  const expr = this.generateNode(node.expression)
730
- this.writeLine(`${expr};`)
898
+ if (expr) {
899
+ this.writeLine(`${expr};`)
900
+ }
901
+ return ''
731
902
  }
732
903
 
733
904
  generateEchoStatement(node) {
734
- const exprs = node.expressions || node.arguments || [node.argument]
735
- const args = exprs.filter(Boolean).map(a => this.generateNode(a)).join(', ')
905
+ const args = (node.expressions || node.arguments || [node.argument]).filter(Boolean).map(a => this.generateNode(a)).join(', ')
736
906
  this.writeLine(`echo ${args};`)
907
+ return ''
737
908
  }
738
909
 
739
- generatePrintStatement(node) {
740
- const arg = this.generateNode(node.argument)
741
- this.writeLine(`print ${arg};`)
742
- }
743
-
744
- generateNamespace(node) {
745
- const name = this.safeString(node.name)
746
- this.writeLine(`namespace ${name};`)
747
- this.writeLine('')
748
- }
749
-
750
- generateUseStatement(node) {
751
- const typePrefix = node.useType === 'function' ? 'function ' : (node.useType === 'const' ? 'const ' : '')
752
-
753
- if (node.imports && node.imports.length > 0) {
754
- for (const imp of node.imports) {
755
- const path = this.safeString(imp.path)
756
- const alias = imp.alias ? ' as ' + imp.alias : ''
757
- this.writeLine(`use ${typePrefix}${path}${alias};`)
758
- }
759
- } else {
760
- const name = this.safeString(node.name || node.source)
761
- const alias = node.alias ? ' as ' + node.alias : ''
762
- this.writeLine(`use ${typePrefix}${name}${alias};`)
763
- }
764
- }
765
-
766
- generateIncludeStatement(node) {
767
- const type = node.once
768
- ? (node.required ? 'require_once' : 'include_once')
769
- : (node.required ? 'require' : 'include')
770
- const path = typeof node.path === 'string' ? `'${node.path}'` : this.generateNode(node.path)
771
- this.writeLine(`${type} ${path};`)
772
- }
773
-
774
- generateInclude(node) {
775
- if (node.resolved && node.children) {
776
- for (const child of node.children) {
777
- this.generateNode(child)
778
- }
910
+ generateBlockStatement(node) {
911
+ for (const stmt of node.body || []) {
912
+ this.generateNode(stmt)
779
913
  }
914
+ return ''
780
915
  }
781
916
 
782
917
  generateCallExpression(node) {
783
918
  let callee
784
919
 
785
920
  if (typeof node.callee === 'string') {
786
- callee = this.translate(node.callee)
787
- } else if (node.callee && node.callee.type === 'MemberExpression') {
788
- callee = this.generateMemberExpression(node.callee)
789
- } else if (node.callee && node.callee.type === 'StaticMemberExpression') {
790
- callee = this.generateStaticMemberExpression(node.callee)
791
- } else if (node.callee && node.callee.type === 'Identifier') {
792
- const name = this.safeString(node.callee.name)
793
- callee = this.translate(name)
921
+ callee = this.translateFunction(node.callee)
922
+ } else if (node.callee.type === 'Identifier') {
923
+ callee = this.translateFunction(node.callee.name)
924
+ } else if (node.callee.type === 'MemberExpression' || node.callee.type === 'StaticMemberExpression') {
925
+ callee = this.generateNode(node.callee)
794
926
  } else {
795
927
  callee = this.generateNode(node.callee)
796
928
  }
797
929
 
798
- const args = (node.arguments || []).map(a => {
799
- if (a && a.type === 'SpreadElement') {
800
- return '...' + this.generateNode(a.argument || a)
801
- }
802
- if (a && a.type === 'NamedArgument') {
803
- return `${a.name}: ${this.generateNode(a.value)}`
804
- }
805
- return this.generateNode(a)
806
- }).join(', ')
807
-
930
+ const args = (node.arguments || []).map(a => this.generateNode(a)).join(', ')
808
931
  return `${callee}(${args})`
809
932
  }
810
933
 
@@ -813,118 +936,69 @@ class PHPGenerator {
813
936
  let property
814
937
 
815
938
  if (typeof node.property === 'string') {
816
- property = this.translateMethodName(node.property)
817
- } else if (node.property && node.property.name) {
818
- property = this.translateMethodName(node.property.name)
819
- } else if (node.property && node.property.type === 'Identifier') {
820
- property = this.translateMethodName(node.property.name || node.property.value)
939
+ property = node.property
940
+ } else if (node.property.type === 'Identifier') {
941
+ property = this.translateFunction(node.property.name)
821
942
  } else {
822
943
  property = this.generateNode(node.property)
823
944
  }
824
945
 
825
- if (node.static) {
826
- return `${object}::${property}`
827
- }
946
+ const operator = node.nullsafe ? '?->' : '->'
947
+
828
948
  if (node.computed) {
829
949
  return `${object}[${property}]`
830
950
  }
831
- if (node.nullsafe || node.operator === '?->') {
832
- return `${object}?->${property}`
833
- }
834
- return `${object}->${property}`
835
- }
836
-
837
- translateMethodName(name) {
838
- if (!name) return name
839
- const translated = this.translate(name)
840
- if (translated.includes('->')) {
841
- const parts = translated.split('->')
842
- return parts[parts.length - 1].replace(/\(\)$/, '')
843
- }
844
- if (translated.includes('::')) {
845
- const parts = translated.split('::')
846
- return parts[parts.length - 1].replace(/\(\)$/, '')
847
- }
848
- return translated.replace(/\(\)$/, '')
951
+ return `${object}${operator}${property}`
849
952
  }
850
953
 
851
954
  generateStaticMemberExpression(node) {
852
- const cls = this.generateNode(node.class)
853
- let member
854
-
855
- if (typeof node.member === 'string') {
856
- member = node.member
857
- } else if (node.member && node.member.name) {
858
- member = node.member.name
859
- } else {
860
- member = this.generateNode(node.member)
861
- }
862
-
955
+ const cls = node.class?.name || this.generateNode(node.class)
956
+ let member = node.member?.name || this.generateNode(node.member)
957
+ member = this.translateKeyword(member)
863
958
  return `${cls}::${member}`
864
959
  }
865
960
 
866
- generateIndexExpression(node) {
867
- const object = this.generateNode(node.object)
868
- const index = node.index ? this.generateNode(node.index) : ''
869
- return `${object}[${index}]`
870
- }
871
-
872
- generateYieldExpression(node) {
873
- const argument = node.argument ? this.generateNode(node.argument) : ''
874
- if (node.delegate) {
875
- return `yield from ${argument}`
876
- }
877
- return argument ? `yield ${argument}` : 'yield'
878
- }
879
-
880
961
  generateBinaryExpression(node) {
881
962
  const left = this.generateNode(node.left)
882
963
  const right = this.generateNode(node.right)
883
964
  let op = node.operator
884
965
 
885
966
  const opMap = {
886
- 'et': '&&', 'and': '&&',
887
- 'ou': '||', 'or': '||',
888
- 'non': '!', 'not': '!',
889
- 'égal': '===', 'egal': '===', 'equal': '===',
890
- 'différent': '!==', 'different': '!==', 'not equal': '!==',
891
- 'concaténer': '.', 'concatener': '.', 'concatenate': '.',
892
- 'vaisseau spatial': '<=>', 'spaceship': '<=>',
893
- 'fusion null': '??', 'null coalescing': '??'
967
+ 'et': '&&',
968
+ 'ou': '||',
969
+ 'concatener': '.',
970
+ 'concat': '.'
894
971
  }
895
-
896
- const opLower = op ? op.toLowerCase() : op
897
- op = opMap[opLower] || op
972
+ op = opMap[this.normalizeAccents(op)] || op
973
+
974
+ return `${left} ${op} ${right}`
975
+ }
898
976
 
977
+ generateAssignmentExpression(node) {
978
+ const left = this.generateNode(node.left)
979
+ const right = this.generateNode(node.right)
980
+ const op = node.operator || '='
899
981
  return `${left} ${op} ${right}`
900
982
  }
901
983
 
902
984
  generateUnaryExpression(node) {
903
- const argument = this.generateNode(node.argument || node.operand)
904
- let op = node.operator
905
- if (op === 'non' || op === 'not') op = '!'
985
+ const operand = this.generateNode(node.operand || node.argument)
986
+ const op = node.operator === 'non' ? '!' : node.operator
906
987
 
907
988
  if (node.prefix !== false) {
908
- return `${op}${argument}`
989
+ return `${op}${operand}`
909
990
  }
910
- return `${argument}${op}`
991
+ return `${operand}${op}`
911
992
  }
912
993
 
913
994
  generateUpdateExpression(node) {
914
- const argument = this.generateNode(node.argument || node.operand)
995
+ const operand = this.generateNode(node.operand || node.argument)
915
996
  const op = node.operator
916
997
 
917
- if (node.prefix !== false) {
918
- return `${op}${argument}`
998
+ if (node.prefix) {
999
+ return `${op}${operand}`
919
1000
  }
920
- return `${argument}${op}`
921
- }
922
-
923
- generateAssignmentExpression(node) {
924
- const left = this.generateNode(node.left)
925
- const right = this.generateNode(node.right)
926
- const op = node.operator || '='
927
- return `${left} ${op} ${right}`
1001
+ return `${operand}${op}`
928
1002
  }
929
1003
 
930
1004
  generateConditionalExpression(node) {
@@ -934,27 +1008,20 @@ class PHPGenerator {
934
1008
  return `${test} ? ${consequent} : ${alternate}`
935
1009
  }
936
1010
 
937
- generateNullCoalescing(node) {
938
- const left = this.generateNode(node.left)
939
- const right = this.generateNode(node.right)
940
- return `${left} ?? ${right}`
941
- }
942
-
943
1011
  generateArrayExpression(node) {
944
1012
  const elements = (node.elements || []).map(e => {
945
- if (!e) return 'null'
946
- if (e.type === 'SpreadElement') {
947
- return '...' + this.generateNode(e.argument || e)
948
- }
949
1013
  if (e.type === 'ArrayElement') {
950
- if (e.key !== null && e.key !== undefined) {
1014
+ if (e.key) {
951
1015
  const key = this.generateNode(e.key)
952
1016
  const value = this.generateNode(e.value)
953
1017
  return `${key} => ${value}`
954
1018
  }
955
1019
  return this.generateNode(e.value)
956
1020
  }
957
- if (e.key !== undefined && e.key !== null) {
1021
+ if (e.type === 'SpreadElement') {
1022
+ return '...' + this.generateNode(e.argument)
1023
+ }
1024
+ if (e.key) {
958
1025
  const key = this.generateNode(e.key)
959
1026
  const value = this.generateNode(e.value || e)
960
1027
  return `${key} => ${value}`
@@ -973,86 +1040,44 @@ class PHPGenerator {
973
1040
  return this.generateArrayExpression(node)
974
1041
  }
975
1042
 
976
- generateArrowFunction(node) {
977
- const params = this.generateParams(node.params || [])
978
- const body = this.generateNode(node.body)
979
- return `fn(${params}) => ${body}`
980
- }
981
-
982
- generateClosure(node) {
983
- const params = this.generateParams(node.params || [])
984
- const useVars = node.uses && node.uses.length > 0
985
- ? ` use (${node.uses.map(u => (u.byRef ? '&' : '') + '$' + this.translate(this.safeString(u.name || u))).join(', ')})`
986
- : ''
987
- const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
1043
+ generateIdentifier(node) {
1044
+ const name = node.name || node
1045
+ if (typeof name !== 'string') return '$var'
988
1046
 
989
- let result = `function(${params})${useVars}${returnType} {\n`
990
- this.indent++
1047
+ const normalized = this.normalizeAccents(name)
991
1048
 
992
- if (node.body && Array.isArray(node.body.body)) {
993
- for (const stmt of node.body.body) {
994
- result += this.getIndent() + this.generateNode(stmt)
995
- }
996
- } else if (node.body) {
997
- result += this.getIndent() + 'return ' + this.generateNode(node.body) + ';\n'
1049
+ if (this.functionMap[normalized]) {
1050
+ return this.functionMap[normalized]
998
1051
  }
999
1052
 
1000
- this.indent--
1001
- result += this.getIndent() + '}'
1002
- return result
1003
- }
1004
-
1005
- generateVariable(node) {
1006
- if (!node) return '$var'
1053
+ const translated = this.translateKeyword(name)
1007
1054
 
1008
- const name = this.safeString(node.name || node.id?.name || node)
1009
- const nameStr = String(name)
1055
+ if (translated.startsWith('$')) return translated
1056
+ if (['true', 'false', 'null', 'self', 'parent', 'static'].includes(translated)) {
1057
+ return translated
1058
+ }
1059
+ if (/^[A-Z]/.test(translated)) return translated
1060
+ if (translated === name && !name.startsWith('$')) {
1061
+ return '$' + name
1062
+ }
1010
1063
 
1011
- if (nameStr.startsWith('$')) return nameStr
1012
- return '$' + nameStr
1064
+ return translated.startsWith('$') ? translated : '$' + translated
1013
1065
  }
1014
1066
 
1015
- generateIdentifier(node) {
1067
+ generateVariable(node) {
1016
1068
  if (!node) return '$var'
1017
1069
 
1018
- const name = this.safeString(node.name || node.id?.name || node)
1019
- const nameStr = String(name)
1070
+ const name = node.name || node.id?.name || node
1071
+ if (typeof name !== 'string') return '$var'
1020
1072
 
1021
- if (nameStr.startsWith('$')) return nameStr
1022
-
1023
- const keywords = [
1024
- 'true', 'false', 'null',
1025
- 'self', 'parent', 'static',
1026
- 'this', '$this'
1027
- ]
1028
- if (keywords.includes(nameStr.toLowerCase())) {
1029
- if (nameStr.toLowerCase() === 'this') return '$this'
1030
- return nameStr
1031
- }
1032
-
1033
- if (/^[A-Z]/.test(nameStr)) return nameStr
1034
-
1035
- if (nameStr.includes('(') || nameStr.includes('::')) return nameStr
1036
-
1037
- const superglobals = ['_GET', '_POST', '_SERVER', '_SESSION', '_COOKIE', '_FILES', '_REQUEST', '_ENV', 'GLOBALS']
1038
- const upper = nameStr.toUpperCase()
1039
- if (superglobals.includes(nameStr) || superglobals.includes(upper)) {
1040
- return '$' + upper
1041
- }
1042
-
1043
- return '$' + nameStr
1073
+ if (name.startsWith('$')) return name
1074
+ return '$' + name
1044
1075
  }
1045
1076
 
1046
1077
  generateLiteral(node) {
1047
- if (node.raw !== undefined) return node.raw
1078
+ if (node.raw) return node.raw
1048
1079
  if (typeof node.value === 'string') {
1049
- const escaped = node.value
1050
- .replace(/\\/g, '\\\\')
1051
- .replace(/"/g, '\\"')
1052
- .replace(/\n/g, '\\n')
1053
- .replace(/\r/g, '\\r')
1054
- .replace(/\t/g, '\\t')
1055
- return `"${escaped}"`
1080
+ return `"${node.value.replace(/"/g, '\\"')}"`
1056
1081
  }
1057
1082
  if (node.value === null) return 'null'
1058
1083
  if (node.value === true) return 'true'
@@ -1061,22 +1086,17 @@ class PHPGenerator {
1061
1086
  }
1062
1087
 
1063
1088
  generateStringLiteral(node) {
1064
- const value = node.value || ''
1065
- const escaped = value
1066
- .replace(/\\/g, '\\\\')
1067
- .replace(/"/g, '\\"')
1068
- .replace(/\n/g, '\\n')
1069
- .replace(/\r/g, '\\r')
1070
- .replace(/\t/g, '\\t')
1071
- return node.doubleQuoted !== false ? `"${escaped}"` : `'${escaped}'`
1089
+ const quote = node.doubleQuoted ? '"' : "'"
1090
+ const value = node.value.replace(new RegExp(quote, 'g'), '\\' + quote)
1091
+ return `${quote}${value}${quote}`
1072
1092
  }
1073
1093
 
1074
1094
  generateNewExpression(node) {
1075
1095
  let callee
1076
1096
  if (typeof node.callee === 'string') {
1077
- callee = this.translate(node.callee)
1078
- } else if (node.callee && node.callee.name) {
1079
- callee = this.translate(node.callee.name)
1097
+ callee = node.callee
1098
+ } else if (node.callee.type === 'Identifier') {
1099
+ callee = node.callee.name
1080
1100
  } else {
1081
1101
  callee = this.generateNode(node.callee)
1082
1102
  }
@@ -1084,20 +1104,64 @@ class PHPGenerator {
1084
1104
  return `new ${callee}(${args})`
1085
1105
  }
1086
1106
 
1087
- generateInstanceOf(node) {
1107
+ generateFunctionExpression(node) {
1108
+ const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
1109
+
1110
+ let uses = ''
1111
+ if (node.uses && node.uses.length > 0) {
1112
+ const useVars = node.uses.map(u => {
1113
+ const ref = u.byRef ? '&' : ''
1114
+ return ref + this.generateVariable(u)
1115
+ }).join(', ')
1116
+ uses = ` use (${useVars})`
1117
+ }
1118
+
1119
+ let returnType = ''
1120
+ if (node.returnType) {
1121
+ returnType = ': ' + this.translateKeyword(node.returnType)
1122
+ }
1123
+
1124
+ let body = ''
1125
+ if (node.body) {
1126
+ const savedOutput = this.output
1127
+ const savedIndent = this.indent
1128
+ this.output = ''
1129
+ this.indent = 0
1130
+ this.generateBody(node.body)
1131
+ body = this.output
1132
+ this.output = savedOutput
1133
+ this.indent = savedIndent
1134
+ }
1135
+
1136
+ return `function(${params})${uses}${returnType} {\n${body}}`
1137
+ }
1138
+
1139
+ generateArrowFunctionExpression(node) {
1140
+ const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
1141
+ const body = this.generateNode(node.body)
1142
+ return `fn(${params}) => ${body}`
1143
+ }
1144
+
1145
+ generateIndexExpression(node) {
1146
+ const object = this.generateNode(node.object)
1147
+ const index = node.index ? this.generateNode(node.index) : ''
1148
+ return `${object}[${index}]`
1149
+ }
1150
+
1151
+ generateNullCoalesceExpression(node) {
1088
1152
  const left = this.generateNode(node.left)
1089
1153
  const right = this.generateNode(node.right)
1090
- return `${left} instanceof ${right}`
1154
+ return `${left} ?? ${right}`
1091
1155
  }
1092
1156
 
1093
- generateBlockStatement(node) {
1094
- for (const stmt of node.body || []) {
1095
- this.generateNode(stmt)
1157
+ generateYieldExpression(node) {
1158
+ if (node.delegate) {
1159
+ return `yield from ${this.generateNode(node.argument)}`
1096
1160
  }
1097
- }
1098
-
1099
- write(text) {
1100
- this.output += text
1161
+ if (node.argument) {
1162
+ return `yield ${this.generateNode(node.argument)}`
1163
+ }
1164
+ return 'yield'
1101
1165
  }
1102
1166
 
1103
1167
  writeLine(text) {
@@ -1109,4 +1173,6 @@ class PHPGenerator {
1109
1173
  }
1110
1174
  }
1111
1175
 
1112
- module.exports = { PHPGenerator }
1176
+ module.exports = {
1177
+ PHPGenerator
1178
+ }