ether-code 0.1.6 → 0.1.8

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 (45) hide show
  1. package/cli/compiler.js +9 -53
  2. package/cli/ether.js +1 -1
  3. package/generators/css-generator.js +42 -55
  4. package/generators/graphql-generator.js +19 -22
  5. package/generators/html-generator.js +51 -220
  6. package/generators/js-generator.js +76 -157
  7. package/generators/node-generator.js +49 -93
  8. package/generators/php-generator.js +46 -68
  9. package/generators/python-generator.js +35 -54
  10. package/generators/react-generator.js +37 -47
  11. package/generators/ruby-generator.js +59 -119
  12. package/generators/sql-generator.js +42 -63
  13. package/generators/ts-generator.js +59 -133
  14. package/i18n/i18n-css.json +147 -147
  15. package/i18n/i18n-graphql.json +6 -6
  16. package/i18n/i18n-html.json +135 -135
  17. package/i18n/i18n-js.json +107 -107
  18. package/i18n/i18n-node.json +14 -14
  19. package/i18n/i18n-php.json +177 -177
  20. package/i18n/i18n-python.json +16 -16
  21. package/i18n/i18n-react.json +97 -97
  22. package/i18n/i18n-ruby.json +22 -22
  23. package/i18n/i18n-sql.json +153 -153
  24. package/i18n/i18n-ts.json +10 -10
  25. package/lexer/ether-lexer.js +175 -34
  26. package/lexer/tokens.js +6 -6
  27. package/package.json +1 -1
  28. package/parsers/ast-css.js +0 -545
  29. package/parsers/ast-graphql.js +0 -424
  30. package/parsers/ast-html.js +0 -886
  31. package/parsers/ast-js.js +0 -750
  32. package/parsers/ast-node.js +0 -2440
  33. package/parsers/ast-php.js +0 -957
  34. package/parsers/ast-react.js +0 -580
  35. package/parsers/ast-ruby.js +0 -895
  36. package/parsers/ast-ts.js +0 -1352
  37. package/parsers/css-parser.js +0 -1981
  38. package/parsers/graphql-parser.js +0 -2011
  39. package/parsers/html-parser.js +0 -1182
  40. package/parsers/js-parser.js +0 -2564
  41. package/parsers/node-parser.js +0 -2644
  42. package/parsers/php-parser.js +0 -3037
  43. package/parsers/react-parser.js +0 -1035
  44. package/parsers/ruby-parser.js +0 -2680
  45. package/parsers/ts-parser.js +0 -3881
@@ -1,3037 +0,0 @@
1
- const fs = require('fs')
2
- const AST = require('./ast-php')
3
-
4
- const PHP_KEYWORDS = {
5
- 'si': 'if', 'if': 'if', 'если': 'if', '如果': 'if', 'もし': 'if',
6
- 'sinon si': 'elseif', 'elseif': 'elseif', 'else if': 'elseif', 'иначе если': 'elseif', '否则如果': 'elseif', 'でなければもし': 'elseif',
7
- 'sinon': 'else', 'else': 'else', 'sino': 'else', 'иначе': 'else', '否则': 'else', 'そうでなければ': 'else',
8
- 'selon': 'switch', 'switch': 'switch', 'segun': 'switch', 'según': 'switch', 'переключатель': 'switch', '开关': 'switch', 'スイッチ': 'switch',
9
- 'cas': 'case', 'case': 'case', 'caso': 'case', 'случай': 'case', '情况': 'case', 'ケース': 'case',
10
- 'defaut': 'default', 'défaut': 'default', 'default': 'default', 'porDefecto': 'default', 'по умолчанию': 'default', '默认': 'default', 'デフォルト': 'default',
11
- 'correspondre': 'match', 'match': 'match', 'coincidir': 'match', 'соответствовать': 'match', '匹配': 'match', 'マッチ': 'match',
12
-
13
- 'pour': 'for', 'for': 'for', 'para': 'for', 'для': 'for', '为': 'for', 'ため': 'for',
14
- 'pour chaque': 'foreach', 'foreach': 'foreach', 'para cada': 'foreach', 'для каждого': 'foreach', '对于每个': 'foreach', '各': 'foreach',
15
- 'tant que': 'while', 'while': 'while', 'mientras': 'while', 'пока': 'while', '当': 'while', 'の間': 'while',
16
- 'faire': 'do', 'do': 'do', 'hacer': 'do', 'делать': 'do', '做': 'do', 'する': 'do',
17
- 'continuer': 'continue', 'continue': 'continue', 'continuar': 'continue', 'продолжить': 'continue', '继续': 'continue', '続ける': 'continue',
18
- 'sortir': 'break', 'break': 'break', 'romper': 'break', 'прервать': 'break', '中断': 'break', 'ブレーク': 'break',
19
-
20
- 'fonction': 'function', 'function': 'function', 'funcion': 'function', 'función': 'function', 'функция': 'function', '函数': 'function', '関数': 'function',
21
- 'retourner': 'return', 'return': 'return', 'retornar': 'return', 'возвращать': 'return', '返回': 'return', '戻る': 'return',
22
-
23
- 'classe': 'class', 'class': 'class', 'clase': 'class', 'класс': 'class', '类': 'class', 'クラス': 'class',
24
- 'nouveau': 'new', 'new': 'new', 'nuevo': 'new', 'новый': 'new', '新建': 'new', '新規': 'new',
25
- 'etendre': 'extends', 'étendre': 'extends', 'extends': 'extends', 'extender': 'extends', 'расширяет': 'extends', '扩展': 'extends', '継承': 'extends',
26
- 'implementer': 'implements', 'implémenter': 'implements', 'implements': 'implements', 'implementar': 'implements', 'реализует': 'implements', '实现': 'implements', '実装': 'implements',
27
- 'interface': 'interface', 'interfaz': 'interface', 'интерфейс': 'interface', '接口': 'interface', 'インターフェース': 'interface',
28
- 'trait': 'trait', 'rasgo': 'trait', 'трейт': 'trait', '特性': 'trait', 'トレイト': 'trait',
29
- 'abstrait': 'abstract', 'abstract': 'abstract', 'abstracto': 'abstract', 'абстрактный': 'abstract', '抽象': 'abstract',
30
- 'final': 'final', 'финальный': 'final', '最终': 'final', 'ファイナル': 'final',
31
- 'lecture seule': 'readonly', 'readonly': 'readonly', 'soloLectura': 'readonly', 'только чтение': 'readonly', '只读': 'readonly', '読み取り専用': 'readonly',
32
-
33
- 'public': 'public', 'publico': 'public', 'público': 'public', 'публичный': 'public', '公共': 'public', 'パブリック': 'public',
34
- 'protege': 'protected', 'protégé': 'protected', 'protected': 'protected', 'protegido': 'protected', 'защищённый': 'protected', '受保护': 'protected', 'プロテクト': 'protected',
35
- 'prive': 'private', 'privé': 'private', 'private': 'private', 'privado': 'private', 'приватный': 'private', '私有': 'private', 'プライベート': 'private',
36
- 'statique': 'static', 'static': 'static', 'estatico': 'static', 'estático': 'static', 'статический': 'static', '静态': 'static', 'スタティック': 'static',
37
-
38
- 'enumeration': 'enum', 'énumération': 'enum', 'enum': 'enum', 'enumeracion': 'enum', 'enumeración': 'enum', 'перечисление': 'enum', '枚举': 'enum', '列挙': 'enum',
39
-
40
- 'espace de noms': 'namespace', 'namespace': 'namespace', 'espacio de nombres': 'namespace', 'пространство имён': 'namespace', '命名空间': 'namespace', '名前空間': 'namespace',
41
- 'utiliser': 'use', 'use': 'use', 'usar': 'use', 'использовать': 'use', '使用': 'use', '使う': 'use',
42
- 'comme': 'as', 'as': 'as', 'como': 'as', 'как': 'as', '作为': 'as', 'として': 'as',
43
-
44
- 'inclure': 'include', 'include': 'include', 'incluir': 'include', 'включить': 'include', '包含': 'include', 'インクルード': 'include',
45
- 'inclure une fois': 'include_once', 'include_once': 'include_once', 'incluir una vez': 'include_once', 'включить один раз': 'include_once', '包含一次': 'include_once',
46
- 'exiger': 'require', 'require': 'require', 'requerir': 'require', 'требовать': 'require', '需要': 'require', '要求': 'require',
47
- 'exiger une fois': 'require_once', 'require_once': 'require_once', 'requerir una vez': 'require_once', 'требовать один раз': 'require_once', '需要一次': 'require_once',
48
-
49
- 'essayer': 'try', 'try': 'try', 'intentar': 'try', 'попробовать': 'try', '尝试': 'try', 'トライ': 'try',
50
- 'attraper': 'catch', 'catch': 'catch', 'capturar': 'catch', 'поймать': 'catch', '捕获': 'catch', 'キャッチ': 'catch',
51
- 'enfin': 'finally', 'finally': 'finally', 'finalmente': 'finally', 'наконец': 'finally', '最终': 'finally', 'ファイナリー': 'finally',
52
- 'lancer': 'throw', 'throw': 'throw', 'lanzar': 'throw', 'бросить': 'throw', '抛出': 'throw', 'スロー': 'throw',
53
-
54
- 'echo': 'echo', 'écho': 'echo', 'eco': 'echo', 'вывод': 'echo', '回显': 'echo', 'エコー': 'echo',
55
- 'imprimer': 'print', 'print': 'print', 'imprimir': 'print', 'печать': 'print', '打印': 'print', '印刷': 'print',
56
-
57
- 'vrai': 'true', 'true': 'true', 'verdadero': 'true', 'истина': 'true', '真': 'true', '真実': 'true',
58
- 'faux': 'false', 'false': 'false', 'falso': 'false', 'ложь': 'false', '假': 'false', '偽': 'false',
59
- 'nul': 'null', 'null': 'null', 'nulo': 'null', 'ноль': 'null', '空值': 'null', 'ヌル': 'null',
60
-
61
- 'global': 'global', 'глобальный': 'global', '全局': 'global', 'グローバル': 'global',
62
- 'const': 'const', 'constante': 'const', 'константа': 'const', '常量': 'const', '定数': 'const',
63
- 'var': 'var', 'variable': 'var', 'переменная': 'var', '变量': 'var', '変数': 'var',
64
-
65
- 'et': 'and', 'and': 'and', 'y': 'and', 'и': 'and', '且': 'and', 'かつ': 'and',
66
- 'ou': 'or', 'or': 'or', 'o': 'or', 'или': 'or', '或': 'or', 'または': 'or',
67
- 'non': 'not', 'not': 'not', 'no': 'not', 'не': 'not', '非': 'not', 'ではない': 'not',
68
- 'ou exclusif': 'xor', 'xor': 'xor', 'o exclusivo': 'xor', 'исключающее или': 'xor', '异或': 'xor', '排他的論理和': 'xor',
69
-
70
- 'instance de': 'instanceof', 'instanceof': 'instanceof', 'instancia de': 'instanceof', 'экземпляр': 'instanceof', '实例': 'instanceof', 'インスタンス': 'instanceof',
71
- 'cloner': 'clone', 'clone': 'clone', 'clonar': 'clone', 'клонировать': 'clone', '克隆': 'clone', 'クローン': 'clone',
72
-
73
- 'soi': 'self', 'self': 'self', 'себя': 'self', '自身': 'self', '自己': 'self',
74
- 'parent': 'parent', 'padre': 'parent', 'родитель': 'parent', '父类': 'parent', '親': 'parent',
75
- 'ceci': '$this', 'esto': '$this', 'это': '$this', '这': '$this', 'これ': '$this',
76
-
77
- 'aller a': 'goto', 'aller à': 'goto', 'goto': 'goto', 'ir a': 'goto', 'перейти': 'goto', '跳转': 'goto', 'ゴートゥ': 'goto',
78
- 'declarer': 'declare', 'déclarer': 'declare', 'declare': 'declare', 'declarar': 'declare', 'объявить': 'declare', '声明': 'declare', '宣言': 'declare',
79
-
80
- 'liste': 'list', 'list': 'list', 'lista': 'list', 'список': 'list', '列表': 'list', 'リスト': 'list',
81
- 'vide': 'empty', 'empty': 'empty', 'vacio': 'empty', 'vacío': 'empty', 'пусто': 'empty', '空': 'empty', '空か': 'empty',
82
- 'existant': 'isset', 'isset': 'isset', 'existe': 'isset', 'существует': 'isset', '已设置': 'isset', '設定済み': 'isset',
83
- 'supprimer': 'unset', 'unset': 'unset', 'eliminar': 'unset', 'удалить': 'unset', '取消设置': 'unset', '解除': 'unset',
84
- 'evaluer': 'eval', 'évaluer': 'eval', 'eval': 'eval', 'evaluar': 'eval', 'выполнить': 'eval', '评估': 'eval', '評価': 'eval',
85
- 'sortie': 'exit', 'exit': 'exit', 'salir': 'exit', 'выход': 'exit', '退出': 'exit', '終了': 'exit',
86
- 'mourir': 'die', 'die': 'die', 'morir': 'die', 'умереть': 'die', '死': 'die', '終わる': 'die',
87
-
88
- 'produire': 'yield', 'yield': 'yield', 'producir': 'yield', 'генерировать': 'yield', '产出': 'yield', '譲る': 'yield',
89
- 'produire de': 'yield from', 'yield from': 'yield from', 'producir de': 'yield from', 'генерировать из': 'yield from', '产出从': 'yield from'
90
- }
91
-
92
- const PHP_TYPES = {
93
- 'chaine': 'string', 'chaîne': 'string', 'string': 'string', 'cadena': 'string', 'строка': 'string', '字符串': 'string', '文字列': 'string',
94
- 'entier': 'int', 'int': 'int', 'integer': 'int', 'entero': 'int', 'целое число': 'int', '整数': 'int',
95
- 'flottant': 'float', 'float': 'float', 'double': 'float', 'flotante': 'float', 'число с плавающей точкой': 'float', '浮点数': 'float', '浮動小数点': 'float',
96
- 'booleen': 'bool', 'booléen': 'bool', 'bool': 'bool', 'boolean': 'bool', 'booleano': 'bool', 'логический': 'bool', '布尔': 'bool', 'ブール': 'bool',
97
- 'tableau': 'array', 'array': 'array', 'arreglo': 'array', 'массив': 'array', '数组': 'array', '配列': 'array',
98
- 'objet': 'object', 'object': 'object', 'objeto': 'object', 'объект': 'object', '对象': 'object', 'オブジェクト': 'object',
99
- 'appelable': 'callable', 'callable': 'callable', 'llamable': 'callable', 'вызываемый': 'callable', '可调用': 'callable', '呼び出し可能': 'callable',
100
- 'iterable': 'iterable', 'itérable': 'iterable', 'итерируемый': 'iterable', '可迭代': 'iterable', 'イテラブル': 'iterable',
101
- 'mixte': 'mixed', 'mixed': 'mixed', 'mixto': 'mixed', 'смешанный': 'mixed', '混合': 'mixed', 'ミックス': 'mixed',
102
- 'void': 'void', 'vide': 'void', 'vacío': 'void', 'пустой': 'void', '空': 'void', 'ボイド': 'void',
103
- 'jamais': 'never', 'never': 'never', 'nunca': 'never', 'никогда': 'never', '从不': 'never', 'ネバー': 'never',
104
- 'ressource': 'resource', 'resource': 'resource', 'recurso': 'resource', 'ресурс': 'resource', '资源': 'resource', 'リソース': 'resource'
105
- }
106
-
107
- class PHPLexer {
108
- constructor(source, i18n = {}) {
109
- this.source = source
110
- this.pos = 0
111
- this.line = 1
112
- this.column = 1
113
- this.tokens = []
114
- this.i18n = i18n
115
- this.inPHP = false
116
- }
117
-
118
- peek(offset = 0) {
119
- return this.source[this.pos + offset] || ''
120
- }
121
-
122
- advance() {
123
- const char = this.source[this.pos]
124
- this.pos++
125
- if (char === '\n') {
126
- this.line++
127
- this.column = 1
128
- } else {
129
- this.column++
130
- }
131
- return char
132
- }
133
-
134
- skipWhitespace() {
135
- while (this.pos < this.source.length && /\s/.test(this.peek())) {
136
- this.advance()
137
- }
138
- }
139
-
140
- skipComment() {
141
- if (this.peek() === '/' && this.peek(1) === '/') {
142
- while (this.pos < this.source.length && this.peek() !== '\n') {
143
- this.advance()
144
- }
145
- return true
146
- }
147
- if (this.peek() === '#' && this.peek(1) !== '[') {
148
- while (this.pos < this.source.length && this.peek() !== '\n') {
149
- this.advance()
150
- }
151
- return true
152
- }
153
- if (this.peek() === '/' && this.peek(1) === '*') {
154
- this.advance()
155
- this.advance()
156
- while (this.pos < this.source.length) {
157
- if (this.peek() === '*' && this.peek(1) === '/') {
158
- this.advance()
159
- this.advance()
160
- break
161
- }
162
- this.advance()
163
- }
164
- return true
165
- }
166
- return false
167
- }
168
-
169
- tokenize() {
170
- while (this.pos < this.source.length) {
171
- this.skipWhitespace()
172
- while (this.skipComment()) {
173
- this.skipWhitespace()
174
- }
175
- if (this.pos >= this.source.length) break
176
-
177
- const loc = { line: this.line, column: this.column }
178
-
179
- if (!this.inPHP) {
180
- if (this.source.slice(this.pos, this.pos + 5) === '<?php') {
181
- this.tokens.push({ type: 'PHP_OPEN', loc })
182
- this.pos += 5
183
- this.inPHP = true
184
- continue
185
- }
186
- if (this.source.slice(this.pos, this.pos + 3) === '<?=') {
187
- this.tokens.push({ type: 'PHP_ECHO_OPEN', loc })
188
- this.pos += 3
189
- this.inPHP = true
190
- continue
191
- }
192
- if (this.source.slice(this.pos, this.pos + 2) === '<?') {
193
- this.tokens.push({ type: 'PHP_SHORT_OPEN', loc })
194
- this.pos += 2
195
- this.inPHP = true
196
- continue
197
- }
198
- let html = ''
199
- while (this.pos < this.source.length) {
200
- if (this.source.slice(this.pos, this.pos + 2) === '<?') break
201
- html += this.advance()
202
- }
203
- if (html) {
204
- this.tokens.push({ type: 'HTML', value: html, loc })
205
- }
206
- continue
207
- }
208
-
209
- if (this.source.slice(this.pos, this.pos + 2) === '?>') {
210
- this.tokens.push({ type: 'PHP_CLOSE', loc })
211
- this.pos += 2
212
- this.inPHP = false
213
- continue
214
- }
215
-
216
- const char = this.peek()
217
-
218
- if (char === '$') {
219
- this.tokens.push(this.readVariable())
220
- continue
221
- }
222
-
223
- if (char === '"' || char === "'") {
224
- this.tokens.push(this.readString(char))
225
- continue
226
- }
227
-
228
- if (char === '`') {
229
- this.tokens.push(this.readShellExec())
230
- continue
231
- }
232
-
233
- if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(this.peek(1)))) {
234
- this.tokens.push(this.readNumber())
235
- continue
236
- }
237
-
238
- if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯_]/.test(char)) {
239
- const { word, loc: wordLoc } = this.readWord()
240
- const tokenType = this.classifyWord(word)
241
- this.tokens.push({ type: tokenType, value: word, loc: wordLoc })
242
- continue
243
- }
244
-
245
- if (char === '#' && this.peek(1) === '[') {
246
- this.tokens.push(this.readAttribute())
247
- continue
248
- }
249
-
250
- const token = this.readOperator()
251
- if (token) {
252
- this.tokens.push(token)
253
- continue
254
- }
255
-
256
- this.advance()
257
- }
258
-
259
- this.tokens.push({ type: 'EOF', loc: { line: this.line, column: this.column } })
260
- return this.tokens
261
- }
262
-
263
- readVariable() {
264
- const loc = { line: this.line, column: this.column }
265
- this.advance()
266
-
267
- if (this.peek() === '$') {
268
- this.advance()
269
- if (this.peek() === '{') {
270
- this.advance()
271
- let expr = ''
272
- let braceCount = 1
273
- while (braceCount > 0 && this.pos < this.source.length) {
274
- const c = this.peek()
275
- if (c === '{') braceCount++
276
- if (c === '}') braceCount--
277
- if (braceCount > 0) expr += this.advance()
278
- else this.advance()
279
- }
280
- return { type: 'VARIABLE_VARIABLE', value: expr, loc }
281
- }
282
- const name = this.readIdentifier()
283
- return { type: 'VARIABLE_VARIABLE', value: name, loc }
284
- }
285
-
286
- if (this.peek() === '{') {
287
- this.advance()
288
- let expr = ''
289
- let braceCount = 1
290
- while (braceCount > 0 && this.pos < this.source.length) {
291
- const c = this.peek()
292
- if (c === '{') braceCount++
293
- if (c === '}') braceCount--
294
- if (braceCount > 0) expr += this.advance()
295
- else this.advance()
296
- }
297
- return { type: 'VARIABLE_EXPR', value: expr, loc }
298
- }
299
-
300
- const name = this.readIdentifier()
301
- return { type: 'VARIABLE', value: name, loc }
302
- }
303
-
304
- readIdentifier() {
305
- let name = ''
306
- while (this.pos < this.source.length && /[a-zA-Z0-9À-ÿА-яぁ-ゟァ-ヿ一-龯_]/.test(this.peek())) {
307
- name += this.advance()
308
- }
309
- return name
310
- }
311
-
312
- readWord() {
313
- const loc = { line: this.line, column: this.column }
314
- let word = ''
315
-
316
- while (this.pos < this.source.length && /[a-zA-Z0-9À-ÿА-яぁ-ゟァ-ヿ一-龯_ ]/.test(this.peek())) {
317
- const nextChar = this.peek()
318
- if (nextChar === ' ') {
319
- const testWord = word + ' ' + this.lookAheadWord()
320
- if (PHP_KEYWORDS[testWord.toLowerCase()]) {
321
- this.advance()
322
- word = testWord
323
- continue
324
- }
325
- break
326
- }
327
- word += this.advance()
328
- }
329
-
330
- return { word: word.trim(), loc }
331
- }
332
-
333
- lookAheadWord() {
334
- let result = ''
335
- let i = this.pos
336
- while (i < this.source.length && /[a-zA-Z0-9À-ÿА-яぁ-ゟァ-ヿ一-龯_]/.test(this.source[i])) {
337
- result += this.source[i]
338
- i++
339
- }
340
- return result
341
- }
342
-
343
- readString(quote) {
344
- const loc = { line: this.line, column: this.column }
345
- this.advance()
346
- let value = ''
347
- let raw = quote
348
-
349
- while (this.pos < this.source.length && this.peek() !== quote) {
350
- if (this.peek() === '\\') {
351
- raw += this.advance()
352
- if (this.pos < this.source.length) {
353
- const escaped = this.advance()
354
- raw += escaped
355
- switch (escaped) {
356
- case 'n': value += '\n'; break
357
- case 'r': value += '\r'; break
358
- case 't': value += '\t'; break
359
- case '\\': value += '\\'; break
360
- case quote: value += quote; break
361
- case '$': value += '$'; break
362
- default: value += '\\' + escaped
363
- }
364
- }
365
- } else {
366
- const c = this.advance()
367
- value += c
368
- raw += c
369
- }
370
- }
371
-
372
- if (this.peek() === quote) {
373
- raw += this.advance()
374
- }
375
-
376
- const isInterpolated = quote === '"' && value.includes('$')
377
- return {
378
- type: isInterpolated ? 'INTERPOLATED_STRING' : 'STRING',
379
- value,
380
- raw,
381
- loc
382
- }
383
- }
384
-
385
- readShellExec() {
386
- const loc = { line: this.line, column: this.column }
387
- this.advance()
388
- let command = ''
389
-
390
- while (this.pos < this.source.length && this.peek() !== '`') {
391
- if (this.peek() === '\\' && this.peek(1) === '`') {
392
- this.advance()
393
- command += this.advance()
394
- } else {
395
- command += this.advance()
396
- }
397
- }
398
-
399
- if (this.peek() === '`') this.advance()
400
-
401
- return { type: 'SHELL_EXEC', value: command, loc }
402
- }
403
-
404
- readNumber() {
405
- const loc = { line: this.line, column: this.column }
406
- let value = ''
407
-
408
- if (this.peek() === '0' && (this.peek(1) === 'x' || this.peek(1) === 'X')) {
409
- value += this.advance() + this.advance()
410
- while (/[0-9a-fA-F_]/.test(this.peek())) {
411
- value += this.advance()
412
- }
413
- return { type: 'NUMBER', value: parseInt(value.replace(/_/g, ''), 16), raw: value, loc }
414
- }
415
-
416
- if (this.peek() === '0' && (this.peek(1) === 'b' || this.peek(1) === 'B')) {
417
- value += this.advance() + this.advance()
418
- while (/[01_]/.test(this.peek())) {
419
- value += this.advance()
420
- }
421
- return { type: 'NUMBER', value: parseInt(value.replace(/_/g, '').slice(2), 2), raw: value, loc }
422
- }
423
-
424
- if (this.peek() === '0' && (this.peek(1) === 'o' || this.peek(1) === 'O')) {
425
- value += this.advance() + this.advance()
426
- while (/[0-7_]/.test(this.peek())) {
427
- value += this.advance()
428
- }
429
- return { type: 'NUMBER', value: parseInt(value.replace(/_/g, '').slice(2), 8), raw: value, loc }
430
- }
431
-
432
- while (/[0-9_]/.test(this.peek())) {
433
- value += this.advance()
434
- }
435
-
436
- if (this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
437
- value += this.advance()
438
- while (/[0-9_]/.test(this.peek())) {
439
- value += this.advance()
440
- }
441
- }
442
-
443
- if (this.peek() === 'e' || this.peek() === 'E') {
444
- value += this.advance()
445
- if (this.peek() === '+' || this.peek() === '-') {
446
- value += this.advance()
447
- }
448
- while (/[0-9_]/.test(this.peek())) {
449
- value += this.advance()
450
- }
451
- }
452
-
453
- const cleanValue = value.replace(/_/g, '')
454
- return {
455
- type: 'NUMBER',
456
- value: cleanValue.includes('.') || cleanValue.includes('e') || cleanValue.includes('E')
457
- ? parseFloat(cleanValue)
458
- : parseInt(cleanValue, 10),
459
- raw: value,
460
- loc
461
- }
462
- }
463
-
464
- readAttribute() {
465
- const loc = { line: this.line, column: this.column }
466
- this.advance()
467
- this.advance()
468
-
469
- let content = ''
470
- let bracketCount = 1
471
-
472
- while (bracketCount > 0 && this.pos < this.source.length) {
473
- const c = this.peek()
474
- if (c === '[') bracketCount++
475
- if (c === ']') bracketCount--
476
- if (bracketCount > 0) content += this.advance()
477
- else this.advance()
478
- }
479
-
480
- return { type: 'ATTRIBUTE', value: content, loc }
481
- }
482
-
483
- readOperator() {
484
- const loc = { line: this.line, column: this.column }
485
-
486
- const threeChar = this.source.slice(this.pos, this.pos + 3)
487
- const twoChar = this.source.slice(this.pos, this.pos + 2)
488
- const oneChar = this.peek()
489
-
490
- const threeCharOps = {
491
- '===': 'IDENTICAL', '!==': 'NOT_IDENTICAL', '<=>': 'SPACESHIP',
492
- '**=': 'POW_ASSIGN', '??=': 'NULL_COALESCE_ASSIGN', '...': 'SPREAD',
493
- '?->': 'NULLSAFE_ARROW'
494
- }
495
-
496
- const twoCharOps = {
497
- '==': 'EQUAL', '!=': 'NOT_EQUAL', '<>': 'NOT_EQUAL',
498
- '<=': 'LTE', '>=': 'GTE', '&&': 'AND', '||': 'OR',
499
- '++': 'INCREMENT', '--': 'DECREMENT', '**': 'POW',
500
- '+=': 'PLUS_ASSIGN', '-=': 'MINUS_ASSIGN', '*=': 'TIMES_ASSIGN',
501
- '/=': 'DIV_ASSIGN', '%=': 'MOD_ASSIGN', '.=': 'CONCAT_ASSIGN',
502
- '&=': 'AND_ASSIGN', '|=': 'OR_ASSIGN', '^=': 'XOR_ASSIGN',
503
- '<<': 'SHL', '>>': 'SHR', '->': 'ARROW', '::': 'DOUBLE_COLON',
504
- '??': 'NULL_COALESCE', '=>': 'DOUBLE_ARROW', 'fn': 'FN'
505
- }
506
-
507
- const oneCharOps = {
508
- '+': 'PLUS', '-': 'MINUS', '*': 'TIMES', '/': 'DIV', '%': 'MOD',
509
- '=': 'ASSIGN', '<': 'LT', '>': 'GT', '!': 'NOT', '~': 'TILDE',
510
- '&': 'AMPERSAND', '|': 'PIPE', '^': 'CARET', '@': 'AT',
511
- '(': 'LPAREN', ')': 'RPAREN', '[': 'LBRACKET', ']': 'RBRACKET',
512
- '{': 'LBRACE', '}': 'RBRACE', ';': 'SEMICOLON', ':': 'COLON',
513
- ',': 'COMMA', '.': 'DOT', '?': 'QUESTION', '\\': 'BACKSLASH'
514
- }
515
-
516
- if (threeCharOps[threeChar]) {
517
- this.advance(); this.advance(); this.advance()
518
- return { type: threeCharOps[threeChar], value: threeChar, loc }
519
- }
520
-
521
- if (twoCharOps[twoChar]) {
522
- this.advance(); this.advance()
523
- return { type: twoCharOps[twoChar], value: twoChar, loc }
524
- }
525
-
526
- if (oneCharOps[oneChar]) {
527
- this.advance()
528
- return { type: oneCharOps[oneChar], value: oneChar, loc }
529
- }
530
-
531
- return null
532
- }
533
-
534
- classifyWord(word) {
535
- const lowerWord = word.toLowerCase()
536
-
537
- if (PHP_KEYWORDS[lowerWord] || PHP_KEYWORDS[word]) {
538
- return 'KEYWORD'
539
- }
540
-
541
- if (PHP_TYPES[lowerWord] || PHP_TYPES[word]) {
542
- return 'TYPE'
543
- }
544
-
545
- return 'IDENTIFIER'
546
- }
547
- }
548
-
549
- class PHPParser {
550
- constructor(i18nPath = null) {
551
- this.tokens = []
552
- this.pos = 0
553
- this.i18n = {}
554
-
555
- if (i18nPath) {
556
- this.loadI18n(i18nPath)
557
- }
558
- }
559
-
560
- loadI18n(filePath) {
561
- try {
562
- const content = fs.readFileSync(filePath, 'utf-8')
563
- this.i18n = JSON.parse(content)
564
- } catch (e) {
565
- console.error(`Erreur chargement i18n: ${e.message}`)
566
- this.i18n = {}
567
- }
568
- }
569
-
570
- setI18n(i18nData) {
571
- this.i18n = i18nData
572
- }
573
-
574
- parse(source) {
575
- const lexer = new PHPLexer(source, this.i18n)
576
- this.tokens = lexer.tokenize()
577
- this.pos = 0
578
- return this.parseProgram()
579
- }
580
-
581
- peek(offset = 0) {
582
- return this.tokens[this.pos + offset] || { type: 'EOF' }
583
- }
584
-
585
- advance() {
586
- return this.tokens[this.pos++]
587
- }
588
-
589
- match(...types) {
590
- return types.includes(this.peek().type)
591
- }
592
-
593
- expect(type) {
594
- const token = this.peek()
595
- if (token.type !== type) {
596
- throw new Error(`Attendu ${type}, reçu ${token.type} à ligne ${token.loc?.line}`)
597
- }
598
- return this.advance()
599
- }
600
-
601
- parseProgram() {
602
- const body = []
603
-
604
- while (!this.match('EOF')) {
605
- const stmt = this.parseTopLevel()
606
- if (stmt) body.push(stmt)
607
- }
608
-
609
- return new AST.Program(body)
610
- }
611
-
612
- parseTopLevel() {
613
- const token = this.peek()
614
-
615
- if (token.type === 'HTML') {
616
- this.advance()
617
- return new AST.HTMLBlock(token.value, token.loc)
618
- }
619
-
620
- if (token.type === 'PHP_OPEN' || token.type === 'PHP_SHORT_OPEN') {
621
- this.advance()
622
- return this.parsePHPBlock()
623
- }
624
-
625
- if (token.type === 'PHP_ECHO_OPEN') {
626
- this.advance()
627
- const expr = this.parseExpression()
628
- if (this.match('PHP_CLOSE')) this.advance()
629
- return new AST.EchoStatement([expr], token.loc)
630
- }
631
-
632
- return this.parseStatement()
633
- }
634
-
635
- parsePHPBlock() {
636
- const body = []
637
-
638
- while (!this.match('PHP_CLOSE', 'EOF')) {
639
- const stmt = this.parseStatement()
640
- if (stmt) body.push(stmt)
641
- }
642
-
643
- if (this.match('PHP_CLOSE')) this.advance()
644
-
645
- return new AST.PHPBlock(body)
646
- }
647
-
648
- parseStatement() {
649
- const token = this.peek()
650
-
651
- if (token.type === 'KEYWORD') {
652
- const keyword = this.translateKeyword(token.value)
653
-
654
- switch (keyword) {
655
- case 'if': return this.parseIfStatement()
656
- case 'switch': return this.parseSwitchStatement()
657
- case 'match': return this.parseMatchExpression()
658
- case 'for': return this.parseForStatement()
659
- case 'foreach': return this.parseForeachStatement()
660
- case 'while': return this.parseWhileStatement()
661
- case 'do': return this.parseDoWhileStatement()
662
- case 'function': return this.parseFunctionDeclaration()
663
- case 'class': return this.parseClassDeclaration()
664
- case 'interface': return this.parseInterfaceDeclaration()
665
- case 'trait': return this.parseTraitDeclaration()
666
- case 'enum': return this.parseEnumDeclaration()
667
- case 'namespace': return this.parseNamespaceDeclaration()
668
- case 'use': return this.parseUseDeclaration()
669
- case 'try': return this.parseTryStatement()
670
- case 'throw': return this.parseThrowStatement()
671
- case 'return': return this.parseReturnStatement()
672
- case 'break': return this.parseBreakStatement()
673
- case 'continue': return this.parseContinueStatement()
674
- case 'echo': return this.parseEchoStatement()
675
- case 'print': return this.parsePrintStatement()
676
- case 'global': return this.parseGlobalStatement()
677
- case 'static': return this.parseStaticStatement()
678
- case 'declare': return this.parseDeclareStatement()
679
- case 'goto': return this.parseGotoStatement()
680
- case 'unset': return this.parseUnsetStatement()
681
- case 'const': return this.parseConstDeclaration()
682
- case 'abstract':
683
- case 'final':
684
- return this.parseClassDeclaration()
685
- }
686
- }
687
-
688
- if (token.type === 'ATTRIBUTE') {
689
- return this.parseAttributedDeclaration()
690
- }
691
-
692
- if (token.type === 'IDENTIFIER' && this.peek(1).type === 'COLON') {
693
- return this.parseLabelStatement()
694
- }
695
-
696
- if (this.match('LBRACE')) {
697
- return this.parseBlockStatement()
698
- }
699
-
700
- if (this.match('SEMICOLON')) {
701
- this.advance()
702
- return new AST.EmptyStatement(token.loc)
703
- }
704
-
705
- const expr = this.parseExpression()
706
- if (this.match('SEMICOLON')) this.advance()
707
-
708
- return new AST.ExpressionStatement(expr, token.loc)
709
- }
710
-
711
- parseIfStatement() {
712
- const loc = this.peek().loc
713
- this.advance()
714
-
715
- this.expect('LPAREN')
716
- const test = this.parseExpression()
717
- this.expect('RPAREN')
718
-
719
- const consequent = this.parseStatementOrBlock()
720
-
721
- let alternate = null
722
- if (this.match('KEYWORD')) {
723
- const kw = this.translateKeyword(this.peek().value)
724
- if (kw === 'elseif') {
725
- alternate = this.parseIfStatement()
726
- } else if (kw === 'else') {
727
- this.advance()
728
- alternate = this.parseStatementOrBlock()
729
- }
730
- }
731
-
732
- return new AST.IfStatement(test, consequent, alternate, loc)
733
- }
734
-
735
- parseSwitchStatement() {
736
- const loc = this.peek().loc
737
- this.advance()
738
-
739
- this.expect('LPAREN')
740
- const discriminant = this.parseExpression()
741
- this.expect('RPAREN')
742
-
743
- this.expect('LBRACE')
744
- const cases = []
745
-
746
- while (!this.match('RBRACE', 'EOF')) {
747
- const caseToken = this.peek()
748
- if (caseToken.type === 'KEYWORD') {
749
- const kw = this.translateKeyword(caseToken.value)
750
- if (kw === 'case') {
751
- this.advance()
752
- const test = this.parseExpression()
753
- this.expect('COLON')
754
- const consequent = []
755
- while (!this.match('RBRACE', 'EOF')) {
756
- const nextKw = this.peek().type === 'KEYWORD' ? this.translateKeyword(this.peek().value) : null
757
- if (nextKw === 'case' || nextKw === 'default') break
758
- consequent.push(this.parseStatement())
759
- }
760
- cases.push(new AST.SwitchCase(test, consequent, caseToken.loc))
761
- } else if (kw === 'default') {
762
- this.advance()
763
- this.expect('COLON')
764
- const consequent = []
765
- while (!this.match('RBRACE', 'EOF')) {
766
- const nextKw = this.peek().type === 'KEYWORD' ? this.translateKeyword(this.peek().value) : null
767
- if (nextKw === 'case' || nextKw === 'default') break
768
- consequent.push(this.parseStatement())
769
- }
770
- cases.push(new AST.SwitchCase(null, consequent, caseToken.loc))
771
- } else {
772
- break
773
- }
774
- } else {
775
- break
776
- }
777
- }
778
-
779
- this.expect('RBRACE')
780
- return new AST.SwitchStatement(discriminant, cases, loc)
781
- }
782
-
783
- parseMatchExpression() {
784
- const loc = this.peek().loc
785
- this.advance()
786
-
787
- this.expect('LPAREN')
788
- const condition = this.parseExpression()
789
- this.expect('RPAREN')
790
-
791
- this.expect('LBRACE')
792
- const arms = []
793
-
794
- while (!this.match('RBRACE', 'EOF')) {
795
- const armLoc = this.peek().loc
796
- let conditions = []
797
- let isDefault = false
798
-
799
- if (this.peek().type === 'KEYWORD' && this.translateKeyword(this.peek().value) === 'default') {
800
- this.advance()
801
- isDefault = true
802
- } else {
803
- conditions.push(this.parseExpression())
804
- while (this.match('COMMA')) {
805
- this.advance()
806
- if (this.match('DOUBLE_ARROW')) break
807
- conditions.push(this.parseExpression())
808
- }
809
- }
810
-
811
- this.expect('DOUBLE_ARROW')
812
- const body = this.parseExpression()
813
-
814
- arms.push(new AST.MatchArm(conditions, body, isDefault, armLoc))
815
-
816
- if (this.match('COMMA')) this.advance()
817
- }
818
-
819
- this.expect('RBRACE')
820
- return new AST.MatchExpression(condition, arms, loc)
821
- }
822
-
823
- parseForStatement() {
824
- const loc = this.peek().loc
825
- this.advance()
826
-
827
- this.expect('LPAREN')
828
-
829
- const init = this.match('SEMICOLON') ? null : this.parseExpressionList()
830
- this.expect('SEMICOLON')
831
-
832
- const test = this.match('SEMICOLON') ? null : this.parseExpressionList()
833
- this.expect('SEMICOLON')
834
-
835
- const update = this.match('RPAREN') ? null : this.parseExpressionList()
836
- this.expect('RPAREN')
837
-
838
- const body = this.parseStatementOrBlock()
839
-
840
- return new AST.ForStatement(init, test, update, body, loc)
841
- }
842
-
843
- parseForeachStatement() {
844
- const loc = this.peek().loc
845
- this.advance()
846
-
847
- this.expect('LPAREN')
848
- const source = this.parseExpression()
849
-
850
- this.expect('KEYWORD')
851
-
852
- let key = null
853
- let value = null
854
- let byRef = false
855
-
856
- if (this.match('AMPERSAND')) {
857
- this.advance()
858
- byRef = true
859
- }
860
-
861
- value = this.parseVariable()
862
-
863
- if (this.match('DOUBLE_ARROW')) {
864
- this.advance()
865
- key = value
866
- byRef = false
867
-
868
- if (this.match('AMPERSAND')) {
869
- this.advance()
870
- byRef = true
871
- }
872
- value = this.parseVariable()
873
- }
874
-
875
- this.expect('RPAREN')
876
- const body = this.parseStatementOrBlock()
877
-
878
- return new AST.ForeachStatement(source, key, value, body, byRef, loc)
879
- }
880
-
881
- parseWhileStatement() {
882
- const loc = this.peek().loc
883
- this.advance()
884
-
885
- this.expect('LPAREN')
886
- const test = this.parseExpression()
887
- this.expect('RPAREN')
888
-
889
- const body = this.parseStatementOrBlock()
890
-
891
- return new AST.WhileStatement(test, body, loc)
892
- }
893
-
894
- parseDoWhileStatement() {
895
- const loc = this.peek().loc
896
- this.advance()
897
-
898
- const body = this.parseStatementOrBlock()
899
-
900
- this.expect('KEYWORD')
901
- this.expect('LPAREN')
902
- const test = this.parseExpression()
903
- this.expect('RPAREN')
904
-
905
- if (this.match('SEMICOLON')) this.advance()
906
-
907
- return new AST.DoWhileStatement(body, test, loc)
908
- }
909
-
910
- parseFunctionDeclaration() {
911
- const loc = this.peek().loc
912
- this.advance()
913
-
914
- let byRef = false
915
- if (this.match('AMPERSAND')) {
916
- this.advance()
917
- byRef = true
918
- }
919
-
920
- const name = this.expect('IDENTIFIER').value
921
-
922
- this.expect('LPAREN')
923
- const params = this.parseParameterList()
924
- this.expect('RPAREN')
925
-
926
- let returnType = null
927
- if (this.match('COLON')) {
928
- this.advance()
929
- returnType = this.parseTypeReference()
930
- }
931
-
932
- const body = this.parseBlockStatement()
933
-
934
- return new AST.FunctionDeclaration(name, params, body, returnType, byRef, loc)
935
- }
936
-
937
- parseParameterList() {
938
- const params = []
939
-
940
- while (!this.match('RPAREN', 'EOF')) {
941
- params.push(this.parseParameter())
942
- if (!this.match('RPAREN')) {
943
- this.expect('COMMA')
944
- }
945
- }
946
-
947
- return params
948
- }
949
-
950
- parseParameter() {
951
- const loc = this.peek().loc
952
- let visibility = null
953
- let readonly = false
954
- let type = null
955
- let byRef = false
956
- let variadic = false
957
-
958
- if (this.match('KEYWORD')) {
959
- const kw = this.translateKeyword(this.peek().value)
960
- if (['public', 'protected', 'private'].includes(kw)) {
961
- visibility = kw
962
- this.advance()
963
- }
964
- }
965
-
966
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'readonly') {
967
- readonly = true
968
- this.advance()
969
- }
970
-
971
- if (this.match('TYPE', 'IDENTIFIER', 'QUESTION')) {
972
- type = this.parseTypeReference()
973
- }
974
-
975
- if (this.match('AMPERSAND')) {
976
- this.advance()
977
- byRef = true
978
- }
979
-
980
- if (this.match('SPREAD')) {
981
- this.advance()
982
- variadic = true
983
- }
984
-
985
- const nameToken = this.expect('VARIABLE')
986
- const name = nameToken.value
987
-
988
- let defaultValue = null
989
- if (this.match('ASSIGN')) {
990
- this.advance()
991
- defaultValue = this.parseExpression()
992
- }
993
-
994
- return new AST.Parameter(name, type, defaultValue, byRef, variadic, visibility, readonly, loc)
995
- }
996
-
997
- parseTypeReference() {
998
- let nullable = false
999
-
1000
- if (this.match('QUESTION')) {
1001
- this.advance()
1002
- nullable = true
1003
- }
1004
-
1005
- const types = [this.parseSingleType()]
1006
-
1007
- while (this.match('PIPE')) {
1008
- this.advance()
1009
- types.push(this.parseSingleType())
1010
- }
1011
-
1012
- if (types.length === 1) {
1013
- types[0].nullable = nullable
1014
- return types[0]
1015
- }
1016
-
1017
- return new AST.UnionType(types)
1018
- }
1019
-
1020
- parseSingleType() {
1021
- const loc = this.peek().loc
1022
- let name = ''
1023
-
1024
- if (this.match('BACKSLASH')) {
1025
- name += this.advance().value
1026
- }
1027
-
1028
- while (this.match('IDENTIFIER', 'TYPE')) {
1029
- name += this.advance().value
1030
- if (this.match('BACKSLASH')) {
1031
- name += this.advance().value
1032
- } else {
1033
- break
1034
- }
1035
- }
1036
-
1037
- return new AST.TypeReference(this.translateType(name) || name, false, loc)
1038
- }
1039
-
1040
- parseClassDeclaration() {
1041
- const loc = this.peek().loc
1042
- const modifiers = []
1043
- const attributes = []
1044
-
1045
- while (this.match('KEYWORD')) {
1046
- const kw = this.translateKeyword(this.peek().value)
1047
- if (['abstract', 'final', 'readonly'].includes(kw)) {
1048
- modifiers.push(kw)
1049
- this.advance()
1050
- } else if (kw === 'class') {
1051
- this.advance()
1052
- break
1053
- } else {
1054
- break
1055
- }
1056
- }
1057
-
1058
- const name = this.expect('IDENTIFIER').value
1059
-
1060
- let superClass = null
1061
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'extends') {
1062
- this.advance()
1063
- superClass = this.parseNamespaceName()
1064
- }
1065
-
1066
- const interfaces = []
1067
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'implements') {
1068
- this.advance()
1069
- interfaces.push(this.parseNamespaceName())
1070
- while (this.match('COMMA')) {
1071
- this.advance()
1072
- interfaces.push(this.parseNamespaceName())
1073
- }
1074
- }
1075
-
1076
- const body = this.parseClassBody()
1077
-
1078
- return new AST.ClassDeclaration(name, body, superClass, interfaces, modifiers, attributes, loc)
1079
- }
1080
-
1081
- parseClassBody() {
1082
- this.expect('LBRACE')
1083
- const members = []
1084
-
1085
- while (!this.match('RBRACE', 'EOF')) {
1086
- const member = this.parseClassMember()
1087
- if (member) members.push(member)
1088
- }
1089
-
1090
- this.expect('RBRACE')
1091
- return new AST.ClassBody(members)
1092
- }
1093
-
1094
- parseClassMember() {
1095
- const loc = this.peek().loc
1096
- const modifiers = []
1097
- const attributes = []
1098
-
1099
- while (this.match('ATTRIBUTE')) {
1100
- attributes.push(this.parseAttributeGroup())
1101
- }
1102
-
1103
- while (this.match('KEYWORD')) {
1104
- const kw = this.translateKeyword(this.peek().value)
1105
- if (['public', 'protected', 'private', 'static', 'abstract', 'final', 'readonly'].includes(kw)) {
1106
- modifiers.push(kw)
1107
- this.advance()
1108
- } else {
1109
- break
1110
- }
1111
- }
1112
-
1113
- if (this.match('KEYWORD')) {
1114
- const kw = this.translateKeyword(this.peek().value)
1115
-
1116
- if (kw === 'function') {
1117
- this.advance()
1118
- return this.parseMethodDefinition(modifiers, attributes, loc)
1119
- }
1120
-
1121
- if (kw === 'const') {
1122
- this.advance()
1123
- return this.parseClassConstant(modifiers, loc)
1124
- }
1125
-
1126
- if (kw === 'use') {
1127
- this.advance()
1128
- return this.parseTraitUse()
1129
- }
1130
- }
1131
-
1132
- return this.parsePropertyDeclaration(modifiers, attributes, loc)
1133
- }
1134
-
1135
- parseMethodDefinition(modifiers, attributes, loc) {
1136
- let byRef = false
1137
- if (this.match('AMPERSAND')) {
1138
- this.advance()
1139
- byRef = true
1140
- }
1141
-
1142
- const name = this.expect('IDENTIFIER').value
1143
-
1144
- this.expect('LPAREN')
1145
- const params = this.parseParameterList()
1146
- this.expect('RPAREN')
1147
-
1148
- let returnType = null
1149
- if (this.match('COLON')) {
1150
- this.advance()
1151
- returnType = this.parseTypeReference()
1152
- }
1153
-
1154
- let body = null
1155
- if (this.match('LBRACE')) {
1156
- body = this.parseBlockStatement()
1157
- } else {
1158
- this.expect('SEMICOLON')
1159
- }
1160
-
1161
- return new AST.MethodDefinition(name, params, body, returnType, modifiers, byRef, attributes, loc)
1162
- }
1163
-
1164
- parsePropertyDeclaration(modifiers, attributes, loc) {
1165
- let type = null
1166
-
1167
- if (this.match('TYPE', 'IDENTIFIER', 'QUESTION')) {
1168
- type = this.parseTypeReference()
1169
- }
1170
-
1171
- const nameToken = this.expect('VARIABLE')
1172
- const name = nameToken.value
1173
-
1174
- let value = null
1175
- if (this.match('ASSIGN')) {
1176
- this.advance()
1177
- value = this.parseExpression()
1178
- }
1179
-
1180
- this.expect('SEMICOLON')
1181
-
1182
- return new AST.PropertyDeclaration(name, value, type, modifiers, attributes, loc)
1183
- }
1184
-
1185
- parseClassConstant(modifiers, loc) {
1186
- let type = null
1187
-
1188
- if (this.match('TYPE', 'IDENTIFIER', 'QUESTION') && this.peek(1).type === 'IDENTIFIER') {
1189
- type = this.parseTypeReference()
1190
- }
1191
-
1192
- const name = this.expect('IDENTIFIER').value
1193
- this.expect('ASSIGN')
1194
- const value = this.parseExpression()
1195
-
1196
- this.expect('SEMICOLON')
1197
-
1198
- return new AST.ClassConstant(name, value, modifiers, type, loc)
1199
- }
1200
-
1201
- parseInterfaceDeclaration() {
1202
- const loc = this.peek().loc
1203
- this.advance()
1204
-
1205
- const name = this.expect('IDENTIFIER').value
1206
-
1207
- const extends_ = []
1208
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'extends') {
1209
- this.advance()
1210
- extends_.push(this.parseNamespaceName())
1211
- while (this.match('COMMA')) {
1212
- this.advance()
1213
- extends_.push(this.parseNamespaceName())
1214
- }
1215
- }
1216
-
1217
- const body = this.parseClassBody()
1218
-
1219
- return new AST.InterfaceDeclaration(name, body, extends_, [], loc)
1220
- }
1221
-
1222
- parseTraitDeclaration() {
1223
- const loc = this.peek().loc
1224
- this.advance()
1225
-
1226
- const name = this.expect('IDENTIFIER').value
1227
- const body = this.parseClassBody()
1228
-
1229
- return new AST.TraitDeclaration(name, body, [], loc)
1230
- }
1231
-
1232
- parseTraitUse() {
1233
- const traits = [this.parseNamespaceName()]
1234
-
1235
- while (this.match('COMMA')) {
1236
- this.advance()
1237
- traits.push(this.parseNamespaceName())
1238
- }
1239
-
1240
- const adaptations = []
1241
-
1242
- if (this.match('LBRACE')) {
1243
- this.advance()
1244
- while (!this.match('RBRACE', 'EOF')) {
1245
- this.advance()
1246
- }
1247
- this.expect('RBRACE')
1248
- } else {
1249
- this.expect('SEMICOLON')
1250
- }
1251
-
1252
- return new AST.TraitUse(traits, adaptations)
1253
- }
1254
-
1255
- parseEnumDeclaration() {
1256
- const loc = this.peek().loc
1257
- this.advance()
1258
-
1259
- const name = this.expect('IDENTIFIER').value
1260
-
1261
- let backingType = null
1262
- if (this.match('COLON')) {
1263
- this.advance()
1264
- backingType = this.parseTypeReference()
1265
- }
1266
-
1267
- const interfaces = []
1268
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'implements') {
1269
- this.advance()
1270
- interfaces.push(this.parseNamespaceName())
1271
- while (this.match('COMMA')) {
1272
- this.advance()
1273
- interfaces.push(this.parseNamespaceName())
1274
- }
1275
- }
1276
-
1277
- this.expect('LBRACE')
1278
- const body = []
1279
-
1280
- while (!this.match('RBRACE', 'EOF')) {
1281
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'case') {
1282
- this.advance()
1283
- const caseName = this.expect('IDENTIFIER').value
1284
- let caseValue = null
1285
- if (this.match('ASSIGN')) {
1286
- this.advance()
1287
- caseValue = this.parseExpression()
1288
- }
1289
- this.expect('SEMICOLON')
1290
- body.push(new AST.EnumCase(caseName, caseValue))
1291
- } else {
1292
- body.push(this.parseClassMember())
1293
- }
1294
- }
1295
-
1296
- this.expect('RBRACE')
1297
-
1298
- return new AST.EnumDeclaration(name, body, backingType, interfaces, [], loc)
1299
- }
1300
-
1301
- parseNamespaceDeclaration() {
1302
- const loc = this.peek().loc
1303
- this.advance()
1304
-
1305
- let name = null
1306
- if (this.match('IDENTIFIER', 'BACKSLASH')) {
1307
- name = this.parseNamespaceName()
1308
- }
1309
-
1310
- if (this.match('LBRACE')) {
1311
- this.advance()
1312
- const body = []
1313
- while (!this.match('RBRACE', 'EOF')) {
1314
- body.push(this.parseStatement())
1315
- }
1316
- this.expect('RBRACE')
1317
- return new AST.NamespaceDeclaration(name, body, loc)
1318
- }
1319
-
1320
- this.expect('SEMICOLON')
1321
- return new AST.NamespaceDeclaration(name, null, loc)
1322
- }
1323
-
1324
- parseUseDeclaration() {
1325
- const loc = this.peek().loc
1326
- this.advance()
1327
-
1328
- let useType = null
1329
- if (this.match('KEYWORD')) {
1330
- const kw = this.translateKeyword(this.peek().value)
1331
- if (kw === 'function' || kw === 'const') {
1332
- useType = kw
1333
- this.advance()
1334
- }
1335
- }
1336
-
1337
- const declarations = []
1338
- declarations.push(this.parseUseClause())
1339
-
1340
- while (this.match('COMMA')) {
1341
- this.advance()
1342
- declarations.push(this.parseUseClause())
1343
- }
1344
-
1345
- this.expect('SEMICOLON')
1346
-
1347
- return new AST.UseDeclaration(declarations, useType, loc)
1348
- }
1349
-
1350
- parseUseClause() {
1351
- const name = this.parseNamespaceName()
1352
-
1353
- let alias = null
1354
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'as') {
1355
- this.advance()
1356
- alias = this.expect('IDENTIFIER').value
1357
- }
1358
-
1359
- return new AST.UseClause(name, alias)
1360
- }
1361
-
1362
- parseNamespaceName() {
1363
- const parts = []
1364
- let absolute = false
1365
-
1366
- if (this.match('BACKSLASH')) {
1367
- absolute = true
1368
- this.advance()
1369
- }
1370
-
1371
- parts.push(this.expect('IDENTIFIER').value)
1372
-
1373
- while (this.match('BACKSLASH')) {
1374
- this.advance()
1375
- parts.push(this.expect('IDENTIFIER').value)
1376
- }
1377
-
1378
- return new AST.NamespaceName(parts, absolute)
1379
- }
1380
-
1381
- parseTryStatement() {
1382
- const loc = this.peek().loc
1383
- this.advance()
1384
-
1385
- const block = this.parseBlockStatement()
1386
- const catches = []
1387
- let finalizer = null
1388
-
1389
- while (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'catch') {
1390
- this.advance()
1391
- this.expect('LPAREN')
1392
-
1393
- const types = [this.parseNamespaceName()]
1394
- while (this.match('PIPE')) {
1395
- this.advance()
1396
- types.push(this.parseNamespaceName())
1397
- }
1398
-
1399
- let param = null
1400
- if (this.match('VARIABLE')) {
1401
- param = this.advance().value
1402
- }
1403
-
1404
- this.expect('RPAREN')
1405
- const catchBody = this.parseBlockStatement()
1406
- catches.push(new AST.CatchClause(types, param, catchBody))
1407
- }
1408
-
1409
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'finally') {
1410
- this.advance()
1411
- finalizer = new AST.FinallyClause(this.parseBlockStatement())
1412
- }
1413
-
1414
- return new AST.TryStatement(block, catches, finalizer, loc)
1415
- }
1416
-
1417
- parseThrowStatement() {
1418
- const loc = this.peek().loc
1419
- this.advance()
1420
-
1421
- const argument = this.parseExpression()
1422
- if (this.match('SEMICOLON')) this.advance()
1423
-
1424
- return new AST.ThrowStatement(argument, loc)
1425
- }
1426
-
1427
- parseReturnStatement() {
1428
- const loc = this.peek().loc
1429
- this.advance()
1430
-
1431
- let argument = null
1432
- if (!this.match('SEMICOLON', 'PHP_CLOSE', 'EOF')) {
1433
- argument = this.parseExpression()
1434
- }
1435
-
1436
- if (this.match('SEMICOLON')) this.advance()
1437
-
1438
- return new AST.ReturnStatement(argument, loc)
1439
- }
1440
-
1441
- parseBreakStatement() {
1442
- const loc = this.peek().loc
1443
- this.advance()
1444
-
1445
- let level = null
1446
- if (this.match('NUMBER')) {
1447
- level = this.advance().value
1448
- }
1449
-
1450
- if (this.match('SEMICOLON')) this.advance()
1451
-
1452
- return new AST.BreakStatement(level, loc)
1453
- }
1454
-
1455
- parseContinueStatement() {
1456
- const loc = this.peek().loc
1457
- this.advance()
1458
-
1459
- let level = null
1460
- if (this.match('NUMBER')) {
1461
- level = this.advance().value
1462
- }
1463
-
1464
- if (this.match('SEMICOLON')) this.advance()
1465
-
1466
- return new AST.ContinueStatement(level, loc)
1467
- }
1468
-
1469
- parseEchoStatement() {
1470
- const loc = this.peek().loc
1471
- this.advance()
1472
-
1473
- const expressions = [this.parseExpression()]
1474
- while (this.match('COMMA')) {
1475
- this.advance()
1476
- expressions.push(this.parseExpression())
1477
- }
1478
-
1479
- if (this.match('SEMICOLON')) this.advance()
1480
-
1481
- return new AST.EchoStatement(expressions, loc)
1482
- }
1483
-
1484
- parsePrintStatement() {
1485
- const loc = this.peek().loc
1486
- this.advance()
1487
-
1488
- const expression = this.parseExpression()
1489
- if (this.match('SEMICOLON')) this.advance()
1490
-
1491
- return new AST.PrintStatement(expression, loc)
1492
- }
1493
-
1494
- parseGlobalStatement() {
1495
- const loc = this.peek().loc
1496
- this.advance()
1497
-
1498
- const variables = [this.parseVariable()]
1499
- while (this.match('COMMA')) {
1500
- this.advance()
1501
- variables.push(this.parseVariable())
1502
- }
1503
-
1504
- if (this.match('SEMICOLON')) this.advance()
1505
-
1506
- return new AST.GlobalStatement(variables, loc)
1507
- }
1508
-
1509
- parseStaticStatement() {
1510
- const loc = this.peek().loc
1511
- this.advance()
1512
-
1513
- if (this.match('KEYWORD')) {
1514
- return this.parseClassDeclaration()
1515
- }
1516
-
1517
- const declarations = []
1518
-
1519
- do {
1520
- if (this.match('COMMA')) this.advance()
1521
- const varToken = this.expect('VARIABLE')
1522
- let value = null
1523
- if (this.match('ASSIGN')) {
1524
- this.advance()
1525
- value = this.parseExpression()
1526
- }
1527
- declarations.push(new AST.StaticVariable(varToken.value, value))
1528
- } while (this.match('COMMA'))
1529
-
1530
- if (this.match('SEMICOLON')) this.advance()
1531
-
1532
- return new AST.StaticStatement(declarations, loc)
1533
- }
1534
-
1535
- parseDeclareStatement() {
1536
- const loc = this.peek().loc
1537
- this.advance()
1538
-
1539
- this.expect('LPAREN')
1540
- const directives = []
1541
-
1542
- do {
1543
- if (this.match('COMMA')) this.advance()
1544
- const name = this.expect('IDENTIFIER').value
1545
- this.expect('ASSIGN')
1546
- const value = this.parseExpression()
1547
- directives.push(new AST.DeclareDirective(name, value))
1548
- } while (this.match('COMMA'))
1549
-
1550
- this.expect('RPAREN')
1551
-
1552
- let body = null
1553
- if (this.match('LBRACE')) {
1554
- body = this.parseBlockStatement()
1555
- } else if (!this.match('SEMICOLON')) {
1556
- body = this.parseStatement()
1557
- }
1558
-
1559
- if (this.match('SEMICOLON')) this.advance()
1560
-
1561
- return new AST.DeclareStatement(directives, body, loc)
1562
- }
1563
-
1564
- parseGotoStatement() {
1565
- const loc = this.peek().loc
1566
- this.advance()
1567
-
1568
- const label = this.expect('IDENTIFIER').value
1569
- if (this.match('SEMICOLON')) this.advance()
1570
-
1571
- return new AST.GotoStatement(label, loc)
1572
- }
1573
-
1574
- parseUnsetStatement() {
1575
- const loc = this.peek().loc
1576
- this.advance()
1577
-
1578
- this.expect('LPAREN')
1579
- const args = [this.parseExpression()]
1580
- while (this.match('COMMA')) {
1581
- this.advance()
1582
- args.push(this.parseExpression())
1583
- }
1584
- this.expect('RPAREN')
1585
-
1586
- if (this.match('SEMICOLON')) this.advance()
1587
-
1588
- return new AST.UnsetStatement(args, loc)
1589
- }
1590
-
1591
- parseConstDeclaration() {
1592
- const loc = this.peek().loc
1593
- this.advance()
1594
-
1595
- const name = this.expect('IDENTIFIER').value
1596
- this.expect('ASSIGN')
1597
- const value = this.parseExpression()
1598
-
1599
- if (this.match('SEMICOLON')) this.advance()
1600
-
1601
- return new AST.ConstantDeclaration(name, value, loc)
1602
- }
1603
-
1604
- parseLabelStatement() {
1605
- const loc = this.peek().loc
1606
- const name = this.advance().value
1607
- this.expect('COLON')
1608
-
1609
- return new AST.LabelStatement(name, loc)
1610
- }
1611
-
1612
- parseAttributeGroup() {
1613
- const token = this.advance()
1614
- const attributes = []
1615
-
1616
- const parts = token.value.split(',')
1617
- for (const part of parts) {
1618
- const trimmed = part.trim()
1619
- if (trimmed) {
1620
- const match = trimmed.match(/^([^(]+)(?:\((.*)\))?$/)
1621
- if (match) {
1622
- attributes.push(new AST.Attribute(match[1].trim(), []))
1623
- }
1624
- }
1625
- }
1626
-
1627
- return new AST.AttributeGroup(attributes)
1628
- }
1629
-
1630
- parseAttributedDeclaration() {
1631
- const attributes = [this.parseAttributeGroup()]
1632
-
1633
- while (this.match('ATTRIBUTE')) {
1634
- attributes.push(this.parseAttributeGroup())
1635
- }
1636
-
1637
- return this.parseStatement()
1638
- }
1639
-
1640
- parseBlockStatement() {
1641
- const loc = this.peek().loc
1642
- this.expect('LBRACE')
1643
- const body = []
1644
-
1645
- while (!this.match('RBRACE', 'EOF')) {
1646
- body.push(this.parseStatement())
1647
- }
1648
-
1649
- this.expect('RBRACE')
1650
- return new AST.BlockStatement(body, loc)
1651
- }
1652
-
1653
- parseStatementOrBlock() {
1654
- if (this.match('LBRACE')) {
1655
- return this.parseBlockStatement()
1656
- }
1657
- return this.parseStatement()
1658
- }
1659
-
1660
- parseExpressionList() {
1661
- const expressions = [this.parseExpression()]
1662
- while (this.match('COMMA')) {
1663
- this.advance()
1664
- expressions.push(this.parseExpression())
1665
- }
1666
- return expressions.length === 1 ? expressions[0] : expressions
1667
- }
1668
-
1669
- parseExpression() {
1670
- return this.parseAssignmentExpression()
1671
- }
1672
-
1673
- parseAssignmentExpression() {
1674
- const left = this.parseConditionalExpression()
1675
-
1676
- if (this.match('ASSIGN', 'PLUS_ASSIGN', 'MINUS_ASSIGN', 'TIMES_ASSIGN', 'DIV_ASSIGN',
1677
- 'MOD_ASSIGN', 'CONCAT_ASSIGN', 'AND_ASSIGN', 'OR_ASSIGN', 'XOR_ASSIGN',
1678
- 'POW_ASSIGN', 'NULL_COALESCE_ASSIGN')) {
1679
- const op = this.advance()
1680
- const right = this.parseAssignmentExpression()
1681
- return new AST.AssignmentExpression(op.value, left, right, left.loc)
1682
- }
1683
-
1684
- return left
1685
- }
1686
-
1687
- parseConditionalExpression() {
1688
- const test = this.parseNullCoalescingExpression()
1689
-
1690
- if (this.match('QUESTION')) {
1691
- this.advance()
1692
- const consequent = this.match('COLON') ? null : this.parseExpression()
1693
- this.expect('COLON')
1694
- const alternate = this.parseConditionalExpression()
1695
- return new AST.ConditionalExpression(test, consequent, alternate, test.loc)
1696
- }
1697
-
1698
- return test
1699
- }
1700
-
1701
- parseNullCoalescingExpression() {
1702
- let left = this.parseLogicalOrExpression()
1703
-
1704
- while (this.match('NULL_COALESCE')) {
1705
- this.advance()
1706
- const right = this.parseLogicalOrExpression()
1707
- left = new AST.NullCoalescingExpression(left, right, left.loc)
1708
- }
1709
-
1710
- return left
1711
- }
1712
-
1713
- parseLogicalOrExpression() {
1714
- let left = this.parseLogicalAndExpression()
1715
-
1716
- while (this.match('OR') || (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'or')) {
1717
- this.advance()
1718
- const right = this.parseLogicalAndExpression()
1719
- left = new AST.BinaryExpression('||', left, right, left.loc)
1720
- }
1721
-
1722
- return left
1723
- }
1724
-
1725
- parseLogicalAndExpression() {
1726
- let left = this.parseBitwiseOrExpression()
1727
-
1728
- while (this.match('AND') || (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'and')) {
1729
- this.advance()
1730
- const right = this.parseBitwiseOrExpression()
1731
- left = new AST.BinaryExpression('&&', left, right, left.loc)
1732
- }
1733
-
1734
- return left
1735
- }
1736
-
1737
- parseBitwiseOrExpression() {
1738
- let left = this.parseBitwiseXorExpression()
1739
-
1740
- while (this.match('PIPE')) {
1741
- this.advance()
1742
- const right = this.parseBitwiseXorExpression()
1743
- left = new AST.BinaryExpression('|', left, right, left.loc)
1744
- }
1745
-
1746
- return left
1747
- }
1748
-
1749
- parseBitwiseXorExpression() {
1750
- let left = this.parseBitwiseAndExpression()
1751
-
1752
- while (this.match('CARET') || (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'xor')) {
1753
- this.advance()
1754
- const right = this.parseBitwiseAndExpression()
1755
- left = new AST.BinaryExpression('^', left, right, left.loc)
1756
- }
1757
-
1758
- return left
1759
- }
1760
-
1761
- parseBitwiseAndExpression() {
1762
- let left = this.parseEqualityExpression()
1763
-
1764
- while (this.match('AMPERSAND') && !this.match('AND')) {
1765
- this.advance()
1766
- const right = this.parseEqualityExpression()
1767
- left = new AST.BinaryExpression('&', left, right, left.loc)
1768
- }
1769
-
1770
- return left
1771
- }
1772
-
1773
- parseEqualityExpression() {
1774
- let left = this.parseComparisonExpression()
1775
-
1776
- while (this.match('EQUAL', 'NOT_EQUAL', 'IDENTICAL', 'NOT_IDENTICAL', 'SPACESHIP')) {
1777
- const op = this.advance()
1778
- const right = this.parseComparisonExpression()
1779
-
1780
- if (op.type === 'SPACESHIP') {
1781
- left = new AST.SpaceshipExpression(left, right, left.loc)
1782
- } else {
1783
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1784
- }
1785
- }
1786
-
1787
- return left
1788
- }
1789
-
1790
- parseComparisonExpression() {
1791
- let left = this.parseShiftExpression()
1792
-
1793
- while (this.match('LT', 'GT', 'LTE', 'GTE')) {
1794
- const op = this.advance()
1795
- const right = this.parseShiftExpression()
1796
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1797
- }
1798
-
1799
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'instanceof') {
1800
- this.advance()
1801
- const right = this.parseUnaryExpression()
1802
- left = new AST.InstanceofExpression(left, right, left.loc)
1803
- }
1804
-
1805
- return left
1806
- }
1807
-
1808
- parseShiftExpression() {
1809
- let left = this.parseAdditiveExpression()
1810
-
1811
- while (this.match('SHL', 'SHR')) {
1812
- const op = this.advance()
1813
- const right = this.parseAdditiveExpression()
1814
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1815
- }
1816
-
1817
- return left
1818
- }
1819
-
1820
- parseAdditiveExpression() {
1821
- let left = this.parseMultiplicativeExpression()
1822
-
1823
- while (this.match('PLUS', 'MINUS', 'DOT')) {
1824
- const op = this.advance()
1825
- const right = this.parseMultiplicativeExpression()
1826
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1827
- }
1828
-
1829
- return left
1830
- }
1831
-
1832
- parseMultiplicativeExpression() {
1833
- let left = this.parsePowExpression()
1834
-
1835
- while (this.match('TIMES', 'DIV', 'MOD')) {
1836
- const op = this.advance()
1837
- const right = this.parsePowExpression()
1838
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1839
- }
1840
-
1841
- return left
1842
- }
1843
-
1844
- parsePowExpression() {
1845
- const left = this.parseUnaryExpression()
1846
-
1847
- if (this.match('POW')) {
1848
- this.advance()
1849
- const right = this.parsePowExpression()
1850
- return new AST.BinaryExpression('**', left, right, left.loc)
1851
- }
1852
-
1853
- return left
1854
- }
1855
-
1856
- parseUnaryExpression() {
1857
- const loc = this.peek().loc
1858
-
1859
- if (this.match('NOT')) {
1860
- this.advance()
1861
- return new AST.UnaryExpression('!', this.parseUnaryExpression(), true, loc)
1862
- }
1863
-
1864
- if (this.match('TILDE')) {
1865
- this.advance()
1866
- return new AST.UnaryExpression('~', this.parseUnaryExpression(), true, loc)
1867
- }
1868
-
1869
- if (this.match('AT')) {
1870
- this.advance()
1871
- return new AST.ErrorSuppressExpression(this.parseUnaryExpression(), loc)
1872
- }
1873
-
1874
- if (this.match('INCREMENT', 'DECREMENT')) {
1875
- const op = this.advance()
1876
- return new AST.UpdateExpression(op.value, this.parseUnaryExpression(), true, loc)
1877
- }
1878
-
1879
- if (this.match('PLUS', 'MINUS')) {
1880
- const op = this.advance()
1881
- return new AST.UnaryExpression(op.value, this.parseUnaryExpression(), true, loc)
1882
- }
1883
-
1884
- if (this.match('LPAREN')) {
1885
- const next = this.peek(1)
1886
- if (next.type === 'TYPE' || next.type === 'IDENTIFIER') {
1887
- const castType = this.lookAheadCast()
1888
- if (castType) {
1889
- this.advance()
1890
- this.advance()
1891
- this.expect('RPAREN')
1892
- return new AST.CastExpression(castType, this.parseUnaryExpression(), loc)
1893
- }
1894
- }
1895
- }
1896
-
1897
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'clone') {
1898
- this.advance()
1899
- return new AST.CloneExpression(this.parseUnaryExpression(), loc)
1900
- }
1901
-
1902
- return this.parsePostfixExpression()
1903
- }
1904
-
1905
- lookAheadCast() {
1906
- const types = ['int', 'integer', 'float', 'double', 'real', 'string', 'binary',
1907
- 'array', 'object', 'bool', 'boolean', 'unset']
1908
- const token = this.peek(1)
1909
- if (token.type === 'TYPE' || token.type === 'IDENTIFIER') {
1910
- const val = token.value.toLowerCase()
1911
- if (types.includes(val) && this.peek(2).type === 'RPAREN') {
1912
- return this.translateType(val) || val
1913
- }
1914
- }
1915
- return null
1916
- }
1917
-
1918
- parsePostfixExpression() {
1919
- let expr = this.parsePrimaryExpression()
1920
-
1921
- while (true) {
1922
- if (this.match('INCREMENT', 'DECREMENT')) {
1923
- const op = this.advance()
1924
- expr = new AST.UpdateExpression(op.value, expr, false, expr.loc)
1925
- } else if (this.match('LBRACKET')) {
1926
- this.advance()
1927
- const index = this.match('RBRACKET') ? null : this.parseExpression()
1928
- this.expect('RBRACKET')
1929
- expr = new AST.ArrayAccess(expr, index, expr.loc)
1930
- } else if (this.match('ARROW', 'NULLSAFE_ARROW')) {
1931
- const nullsafe = this.peek().type === 'NULLSAFE_ARROW'
1932
- this.advance()
1933
- if (this.match('LBRACE')) {
1934
- this.advance()
1935
- const property = this.parseExpression()
1936
- this.expect('RBRACE')
1937
- expr = new AST.MemberExpression(expr, property, nullsafe, expr.loc)
1938
- } else {
1939
- const property = this.parsePropertyName()
1940
- expr = new AST.MemberExpression(expr, property, nullsafe, expr.loc)
1941
- }
1942
- if (this.match('LPAREN')) {
1943
- expr = this.parseCallArguments(expr)
1944
- }
1945
- } else if (this.match('DOUBLE_COLON')) {
1946
- this.advance()
1947
- const property = this.parsePropertyName()
1948
- expr = new AST.StaticMemberExpression(expr, property, expr.loc)
1949
- if (this.match('LPAREN')) {
1950
- expr = this.parseCallArguments(expr)
1951
- }
1952
- } else if (this.match('LPAREN')) {
1953
- expr = this.parseCallArguments(expr)
1954
- } else {
1955
- break
1956
- }
1957
- }
1958
-
1959
- return expr
1960
- }
1961
-
1962
- parseCallArguments(callee) {
1963
- this.expect('LPAREN')
1964
- const args = []
1965
-
1966
- while (!this.match('RPAREN', 'EOF')) {
1967
- if (this.match('IDENTIFIER') && this.peek(1).type === 'COLON') {
1968
- const name = this.advance().value
1969
- this.expect('COLON')
1970
- const value = this.parseExpression()
1971
- args.push(new AST.NamedArgument(name, value))
1972
- } else if (this.match('SPREAD')) {
1973
- this.advance()
1974
- const value = this.parseExpression()
1975
- args.push(new AST.ArrayElement(value, null, false, true))
1976
- } else {
1977
- args.push(this.parseExpression())
1978
- }
1979
-
1980
- if (!this.match('RPAREN')) {
1981
- this.expect('COMMA')
1982
- }
1983
- }
1984
-
1985
- this.expect('RPAREN')
1986
- return new AST.CallExpression(callee, args, callee.loc)
1987
- }
1988
-
1989
- parsePropertyName() {
1990
- if (this.match('VARIABLE')) {
1991
- return this.parseVariable()
1992
- }
1993
- if (this.match('IDENTIFIER', 'KEYWORD')) {
1994
- return new AST.Identifier(this.advance().value)
1995
- }
1996
- throw new Error(`Attendu nom de propriété à ligne ${this.peek().loc?.line}`)
1997
- }
1998
-
1999
- parsePrimaryExpression() {
2000
- const token = this.peek()
2001
-
2002
- if (token.type === 'VARIABLE') {
2003
- return this.parseVariable()
2004
- }
2005
-
2006
- if (token.type === 'VARIABLE_VARIABLE') {
2007
- this.advance()
2008
- return new AST.VariableVariable(token.value, token.loc)
2009
- }
2010
-
2011
- if (token.type === 'NUMBER') {
2012
- this.advance()
2013
- return new AST.NumericLiteral(token.value, token.raw, token.loc)
2014
- }
2015
-
2016
- if (token.type === 'STRING') {
2017
- this.advance()
2018
- return new AST.StringLiteral(token.value, token.raw, token.loc)
2019
- }
2020
-
2021
- if (token.type === 'INTERPOLATED_STRING') {
2022
- this.advance()
2023
- return new AST.InterpolatedString([token.value], token.loc)
2024
- }
2025
-
2026
- if (token.type === 'SHELL_EXEC') {
2027
- this.advance()
2028
- return new AST.ShellExecExpression(token.value, token.loc)
2029
- }
2030
-
2031
- if (token.type === 'KEYWORD') {
2032
- const kw = this.translateKeyword(token.value)
2033
-
2034
- if (kw === 'true') {
2035
- this.advance()
2036
- return new AST.BooleanLiteral(true, token.loc)
2037
- }
2038
- if (kw === 'false') {
2039
- this.advance()
2040
- return new AST.BooleanLiteral(false, token.loc)
2041
- }
2042
- if (kw === 'null') {
2043
- this.advance()
2044
- return new AST.NullLiteral(token.loc)
2045
- }
2046
-
2047
- if (kw === 'new') {
2048
- return this.parseNewExpression()
2049
- }
2050
-
2051
- if (kw === 'array') {
2052
- return this.parseArrayExpression(false)
2053
- }
2054
-
2055
- if (kw === 'list') {
2056
- return this.parseListExpression()
2057
- }
2058
-
2059
- if (kw === 'function') {
2060
- return this.parseClosureExpression()
2061
- }
2062
-
2063
- if (kw === 'static' && this.peek(1).type === 'KEYWORD') {
2064
- this.advance()
2065
- return this.parseClosureExpression(true)
2066
- }
2067
-
2068
- if (kw === 'fn') {
2069
- return this.parseArrowFunction()
2070
- }
2071
-
2072
- if (kw === 'yield') {
2073
- return this.parseYieldExpression()
2074
- }
2075
-
2076
- if (kw === 'throw') {
2077
- this.advance()
2078
- return new AST.ThrowExpression(this.parseExpression(), token.loc)
2079
- }
2080
-
2081
- if (kw === 'empty') {
2082
- this.advance()
2083
- this.expect('LPAREN')
2084
- const arg = this.parseExpression()
2085
- this.expect('RPAREN')
2086
- return new AST.EmptyExpression(arg, token.loc)
2087
- }
2088
-
2089
- if (kw === 'isset') {
2090
- this.advance()
2091
- this.expect('LPAREN')
2092
- const args = [this.parseExpression()]
2093
- while (this.match('COMMA')) {
2094
- this.advance()
2095
- args.push(this.parseExpression())
2096
- }
2097
- this.expect('RPAREN')
2098
- return new AST.IssetExpression(args, token.loc)
2099
- }
2100
-
2101
- if (kw === 'eval') {
2102
- this.advance()
2103
- this.expect('LPAREN')
2104
- const code = this.parseExpression()
2105
- this.expect('RPAREN')
2106
- return new AST.EvalExpression(code, token.loc)
2107
- }
2108
-
2109
- if (kw === 'exit' || kw === 'die') {
2110
- this.advance()
2111
- let arg = null
2112
- if (this.match('LPAREN')) {
2113
- this.advance()
2114
- if (!this.match('RPAREN')) {
2115
- arg = this.parseExpression()
2116
- }
2117
- this.expect('RPAREN')
2118
- }
2119
- return new AST.ExitExpression(arg, kw === 'die', token.loc)
2120
- }
2121
-
2122
- if (kw === 'print') {
2123
- this.advance()
2124
- return new AST.PrintExpression(this.parseExpression(), token.loc)
2125
- }
2126
-
2127
- if (kw === 'include' || kw === 'include_once' || kw === 'require' || kw === 'require_once') {
2128
- this.advance()
2129
- const path = this.parseExpression()
2130
- return new AST.IncludeExpression(path, kw.includes('once'), kw.includes('require'), token.loc)
2131
- }
2132
-
2133
- if (kw === 'self' || kw === 'parent' || kw === 'static') {
2134
- this.advance()
2135
- return new AST.Identifier(kw, token.loc)
2136
- }
2137
-
2138
- if (kw === 'match') {
2139
- return this.parseMatchExpression()
2140
- }
2141
- }
2142
-
2143
- if (token.type === 'FN') {
2144
- return this.parseArrowFunction()
2145
- }
2146
-
2147
- if (token.type === 'LBRACKET') {
2148
- return this.parseArrayExpression(true)
2149
- }
2150
-
2151
- if (token.type === 'LPAREN') {
2152
- this.advance()
2153
- const expr = this.parseExpression()
2154
- this.expect('RPAREN')
2155
- return expr
2156
- }
2157
-
2158
- if (token.type === 'IDENTIFIER' || token.type === 'BACKSLASH') {
2159
- return this.parseNameOrCall()
2160
- }
2161
-
2162
- throw new Error(`Token inattendu ${token.type} à ligne ${token.loc?.line}`)
2163
- }
2164
-
2165
- parseVariable() {
2166
- const token = this.expect('VARIABLE')
2167
- return new AST.Variable(token.value, token.loc)
2168
- }
2169
-
2170
- parseNewExpression() {
2171
- const loc = this.peek().loc
2172
- this.advance()
2173
-
2174
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'class') {
2175
- this.advance()
2176
- return this.parseAnonymousClass(loc)
2177
- }
2178
-
2179
- const callee = this.parseNameOrCall()
2180
-
2181
- let args = []
2182
- if (this.match('LPAREN')) {
2183
- this.advance()
2184
- while (!this.match('RPAREN', 'EOF')) {
2185
- args.push(this.parseExpression())
2186
- if (!this.match('RPAREN')) {
2187
- this.expect('COMMA')
2188
- }
2189
- }
2190
- this.expect('RPAREN')
2191
- }
2192
-
2193
- return new AST.NewExpression(callee, args, loc)
2194
- }
2195
-
2196
- parseAnonymousClass(loc) {
2197
- let args = []
2198
- if (this.match('LPAREN')) {
2199
- this.advance()
2200
- while (!this.match('RPAREN', 'EOF')) {
2201
- args.push(this.parseExpression())
2202
- if (!this.match('RPAREN')) {
2203
- this.expect('COMMA')
2204
- }
2205
- }
2206
- this.expect('RPAREN')
2207
- }
2208
-
2209
- let superClass = null
2210
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'extends') {
2211
- this.advance()
2212
- superClass = this.parseNamespaceName()
2213
- }
2214
-
2215
- const interfaces = []
2216
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'implements') {
2217
- this.advance()
2218
- interfaces.push(this.parseNamespaceName())
2219
- while (this.match('COMMA')) {
2220
- this.advance()
2221
- interfaces.push(this.parseNamespaceName())
2222
- }
2223
- }
2224
-
2225
- const body = this.parseClassBody()
2226
-
2227
- return new AST.AnonymousClass(args, superClass, interfaces, body, loc)
2228
- }
2229
-
2230
- parseArrayExpression(shortSyntax) {
2231
- const loc = this.peek().loc
2232
-
2233
- if (shortSyntax) {
2234
- this.expect('LBRACKET')
2235
- } else {
2236
- this.advance()
2237
- this.expect('LPAREN')
2238
- }
2239
-
2240
- const elements = []
2241
- const closeToken = shortSyntax ? 'RBRACKET' : 'RPAREN'
2242
-
2243
- while (!this.match(closeToken, 'EOF')) {
2244
- if (this.match('SPREAD')) {
2245
- this.advance()
2246
- const value = this.parseExpression()
2247
- elements.push(new AST.ArrayElement(value, null, false, true))
2248
- } else {
2249
- let key = null
2250
- let value = this.parseExpression()
2251
- let byRef = false
2252
-
2253
- if (this.match('DOUBLE_ARROW')) {
2254
- this.advance()
2255
- key = value
2256
-
2257
- if (this.match('AMPERSAND')) {
2258
- this.advance()
2259
- byRef = true
2260
- }
2261
-
2262
- value = this.parseExpression()
2263
- }
2264
-
2265
- elements.push(new AST.ArrayElement(value, key, byRef, false))
2266
- }
2267
-
2268
- if (!this.match(closeToken)) {
2269
- this.expect('COMMA')
2270
- if (this.match(closeToken)) break
2271
- }
2272
- }
2273
-
2274
- this.expect(closeToken)
2275
- return new AST.ArrayExpression(elements, shortSyntax, loc)
2276
- }
2277
-
2278
- parseListExpression() {
2279
- const loc = this.peek().loc
2280
- this.advance()
2281
- this.expect('LPAREN')
2282
-
2283
- const elements = []
2284
-
2285
- while (!this.match('RPAREN', 'EOF')) {
2286
- if (this.match('COMMA')) {
2287
- elements.push(null)
2288
- } else {
2289
- let key = null
2290
- let value = this.parseExpression()
2291
-
2292
- if (this.match('DOUBLE_ARROW')) {
2293
- this.advance()
2294
- key = value
2295
- value = this.parseExpression()
2296
- }
2297
-
2298
- elements.push(new AST.ArrayElement(value, key, false, false))
2299
- }
2300
-
2301
- if (!this.match('RPAREN')) {
2302
- this.expect('COMMA')
2303
- }
2304
- }
2305
-
2306
- this.expect('RPAREN')
2307
- return new AST.ListExpression(elements, loc)
2308
- }
2309
-
2310
- parseClosureExpression(isStatic = false) {
2311
- const loc = this.peek().loc
2312
- this.advance()
2313
-
2314
- let byRef = false
2315
- if (this.match('AMPERSAND')) {
2316
- this.advance()
2317
- byRef = true
2318
- }
2319
-
2320
- this.expect('LPAREN')
2321
- const params = this.parseParameterList()
2322
- this.expect('RPAREN')
2323
-
2324
- const uses = []
2325
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'use') {
2326
- this.advance()
2327
- this.expect('LPAREN')
2328
-
2329
- while (!this.match('RPAREN', 'EOF')) {
2330
- let useByRef = false
2331
- if (this.match('AMPERSAND')) {
2332
- this.advance()
2333
- useByRef = true
2334
- }
2335
- const useVar = this.expect('VARIABLE').value
2336
- uses.push(new AST.ClosureUse(useVar, useByRef))
2337
-
2338
- if (!this.match('RPAREN')) {
2339
- this.expect('COMMA')
2340
- }
2341
- }
2342
-
2343
- this.expect('RPAREN')
2344
- }
2345
-
2346
- let returnType = null
2347
- if (this.match('COLON')) {
2348
- this.advance()
2349
- returnType = this.parseTypeReference()
2350
- }
2351
-
2352
- const body = this.parseBlockStatement()
2353
-
2354
- return new AST.ClosureExpression(params, uses, body, returnType, isStatic, loc)
2355
- }
2356
-
2357
- parseArrowFunction() {
2358
- const loc = this.peek().loc
2359
- let isStatic = false
2360
-
2361
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'static') {
2362
- isStatic = true
2363
- this.advance()
2364
- }
2365
-
2366
- this.advance()
2367
-
2368
- this.expect('LPAREN')
2369
- const params = this.parseParameterList()
2370
- this.expect('RPAREN')
2371
-
2372
- let returnType = null
2373
- if (this.match('COLON')) {
2374
- this.advance()
2375
- returnType = this.parseTypeReference()
2376
- }
2377
-
2378
- this.expect('DOUBLE_ARROW')
2379
- const body = this.parseExpression()
2380
-
2381
- return new AST.ArrowFunction(params, body, returnType, isStatic, loc)
2382
- }
2383
-
2384
- parseYieldExpression() {
2385
- const loc = this.peek().loc
2386
- this.advance()
2387
-
2388
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'from') {
2389
- this.advance()
2390
- return new AST.YieldFromExpression(this.parseExpression(), loc)
2391
- }
2392
-
2393
- if (this.match('SEMICOLON', 'RPAREN', 'RBRACKET', 'COMMA')) {
2394
- return new AST.YieldExpression(null, null, loc)
2395
- }
2396
-
2397
- const first = this.parseExpression()
2398
-
2399
- if (this.match('DOUBLE_ARROW')) {
2400
- this.advance()
2401
- const value = this.parseExpression()
2402
- return new AST.YieldExpression(first, value, loc)
2403
- }
2404
-
2405
- return new AST.YieldExpression(null, first, loc)
2406
- }
2407
-
2408
- parseNameOrCall() {
2409
- const name = this.parseNamespaceName()
2410
-
2411
- if (this.match('LPAREN')) {
2412
- return this.parseCallArguments(name)
2413
- }
2414
-
2415
- if (this.match('DOUBLE_COLON')) {
2416
- this.advance()
2417
- const property = this.parsePropertyName()
2418
- const expr = new AST.StaticMemberExpression(name, property, name.loc)
2419
-
2420
- if (this.match('LPAREN')) {
2421
- return this.parseCallArguments(expr)
2422
- }
2423
- return expr
2424
- }
2425
-
2426
- if (name.parts.length === 1 && !name.absolute) {
2427
- return new AST.Identifier(name.parts[0], name.loc)
2428
- }
2429
-
2430
- return name
2431
- }
2432
-
2433
- translateKeyword(word) {
2434
- const lowerWord = word.toLowerCase()
2435
- return PHP_KEYWORDS[lowerWord] || PHP_KEYWORDS[word] || word
2436
- }
2437
-
2438
- translateType(word) {
2439
- const lowerWord = word.toLowerCase()
2440
- return PHP_TYPES[lowerWord] || PHP_TYPES[word] || null
2441
- }
2442
- }
2443
-
2444
- class PHPCodeGenerator {
2445
- constructor(options = {}) {
2446
- this.indentStr = options.indentStr || ' '
2447
- this.indentLevel = 0
2448
- }
2449
-
2450
- indent() {
2451
- return this.indentStr.repeat(this.indentLevel)
2452
- }
2453
-
2454
- generate(ast) {
2455
- if (!ast) return ''
2456
-
2457
- const method = `generate${ast.type}`
2458
- if (this[method]) {
2459
- return this[method](ast)
2460
- }
2461
-
2462
- return ''
2463
- }
2464
-
2465
- generateProgram(node) {
2466
- return node.body.map(stmt => this.generate(stmt)).join('\n')
2467
- }
2468
-
2469
- generatePHPBlock(node) {
2470
- return '<?php\n' + node.body.map(stmt => this.generate(stmt)).join('\n') + '\n?>'
2471
- }
2472
-
2473
- generateHTMLBlock(node) {
2474
- return node.content
2475
- }
2476
-
2477
- generateEchoStatement(node) {
2478
- const exprs = node.expressions.map(e => this.generate(e)).join(', ')
2479
- return `${this.indent()}echo ${exprs};`
2480
- }
2481
-
2482
- generatePrintStatement(node) {
2483
- return `${this.indent()}print ${this.generate(node.expression)};`
2484
- }
2485
-
2486
- generateIdentifier(node) {
2487
- return node.name
2488
- }
2489
-
2490
- generateVariable(node) {
2491
- return '$' + node.name
2492
- }
2493
-
2494
- generateVariableVariable(node) {
2495
- return '$$' + node.expression
2496
- }
2497
-
2498
- generateConstantDeclaration(node) {
2499
- return `${this.indent()}const ${node.name} = ${this.generate(node.value)};`
2500
- }
2501
-
2502
- generateStringLiteral(node) {
2503
- return node.raw || `"${node.value}"`
2504
- }
2505
-
2506
- generateNumericLiteral(node) {
2507
- return node.raw || String(node.value)
2508
- }
2509
-
2510
- generateBooleanLiteral(node) {
2511
- return node.value ? 'true' : 'false'
2512
- }
2513
-
2514
- generateNullLiteral() {
2515
- return 'null'
2516
- }
2517
-
2518
- generateArrayExpression(node) {
2519
- const elements = node.elements.map(e => this.generate(e)).join(', ')
2520
- return node.shortSyntax ? `[${elements}]` : `array(${elements})`
2521
- }
2522
-
2523
- generateArrayElement(node) {
2524
- let result = ''
2525
- if (node.unpack) result += '...'
2526
- if (node.key) result += this.generate(node.key) + ' => '
2527
- if (node.byRef) result += '&'
2528
- result += this.generate(node.value)
2529
- return result
2530
- }
2531
-
2532
- generateBinaryExpression(node) {
2533
- return `${this.generate(node.left)} ${node.operator} ${this.generate(node.right)}`
2534
- }
2535
-
2536
- generateUnaryExpression(node) {
2537
- if (node.prefix) {
2538
- return `${node.operator}${this.generate(node.argument)}`
2539
- }
2540
- return `${this.generate(node.argument)}${node.operator}`
2541
- }
2542
-
2543
- generateUpdateExpression(node) {
2544
- if (node.prefix) {
2545
- return `${node.operator}${this.generate(node.argument)}`
2546
- }
2547
- return `${this.generate(node.argument)}${node.operator}`
2548
- }
2549
-
2550
- generateAssignmentExpression(node) {
2551
- return `${this.generate(node.left)} ${node.operator} ${this.generate(node.right)}`
2552
- }
2553
-
2554
- generateConditionalExpression(node) {
2555
- const test = this.generate(node.test)
2556
- const consequent = node.consequent ? this.generate(node.consequent) : ''
2557
- const alternate = this.generate(node.alternate)
2558
- return `${test} ? ${consequent} : ${alternate}`
2559
- }
2560
-
2561
- generateNullCoalescingExpression(node) {
2562
- return `${this.generate(node.left)} ?? ${this.generate(node.right)}`
2563
- }
2564
-
2565
- generateSpaceshipExpression(node) {
2566
- return `${this.generate(node.left)} <=> ${this.generate(node.right)}`
2567
- }
2568
-
2569
- generateInstanceofExpression(node) {
2570
- return `${this.generate(node.left)} instanceof ${this.generate(node.right)}`
2571
- }
2572
-
2573
- generateCastExpression(node) {
2574
- return `(${node.castType})${this.generate(node.expression)}`
2575
- }
2576
-
2577
- generateCloneExpression(node) {
2578
- return `clone ${this.generate(node.expression)}`
2579
- }
2580
-
2581
- generateErrorSuppressExpression(node) {
2582
- return `@${this.generate(node.expression)}`
2583
- }
2584
-
2585
- generateIfStatement(node) {
2586
- let result = `${this.indent()}if (${this.generate(node.test)}) `
2587
- result += this.generateBlock(node.consequent)
2588
-
2589
- if (node.alternate) {
2590
- if (node.alternate.type === 'IfStatement') {
2591
- result += ` else${this.generate(node.alternate).trim().slice(this.indent().length)}`
2592
- } else {
2593
- result += ` else ${this.generateBlock(node.alternate)}`
2594
- }
2595
- }
2596
-
2597
- return result
2598
- }
2599
-
2600
- generateBlock(node) {
2601
- if (node.type === 'BlockStatement') {
2602
- return this.generate(node)
2603
- }
2604
- this.indentLevel++
2605
- const result = `{\n${this.generate(node)}\n${this.indent().slice(this.indentStr.length)}}`
2606
- this.indentLevel--
2607
- return result
2608
- }
2609
-
2610
- generateBlockStatement(node) {
2611
- this.indentLevel++
2612
- const body = node.body.map(stmt => this.generate(stmt)).join('\n')
2613
- this.indentLevel--
2614
- return `{\n${body}\n${this.indent()}}`
2615
- }
2616
-
2617
- generateSwitchStatement(node) {
2618
- let result = `${this.indent()}switch (${this.generate(node.discriminant)}) {\n`
2619
- this.indentLevel++
2620
- for (const c of node.cases) {
2621
- result += this.generate(c) + '\n'
2622
- }
2623
- this.indentLevel--
2624
- result += `${this.indent()}}`
2625
- return result
2626
- }
2627
-
2628
- generateSwitchCase(node) {
2629
- let result = node.test
2630
- ? `${this.indent()}case ${this.generate(node.test)}:\n`
2631
- : `${this.indent()}default:\n`
2632
- this.indentLevel++
2633
- for (const stmt of node.consequent) {
2634
- result += this.generate(stmt) + '\n'
2635
- }
2636
- this.indentLevel--
2637
- return result.trimEnd()
2638
- }
2639
-
2640
- generateMatchExpression(node) {
2641
- let result = `match (${this.generate(node.condition)}) {\n`
2642
- this.indentLevel++
2643
- for (const arm of node.arms) {
2644
- result += this.generate(arm) + ',\n'
2645
- }
2646
- this.indentLevel--
2647
- result += `${this.indent()}}`
2648
- return result
2649
- }
2650
-
2651
- generateMatchArm(node) {
2652
- const conditions = node.isDefault
2653
- ? 'default'
2654
- : node.conditions.map(c => this.generate(c)).join(', ')
2655
- return `${this.indent()}${conditions} => ${this.generate(node.body)}`
2656
- }
2657
-
2658
- generateForStatement(node) {
2659
- const init = node.init ? (Array.isArray(node.init) ? node.init.map(e => this.generate(e)).join(', ') : this.generate(node.init)) : ''
2660
- const test = node.test ? (Array.isArray(node.test) ? node.test.map(e => this.generate(e)).join(', ') : this.generate(node.test)) : ''
2661
- const update = node.update ? (Array.isArray(node.update) ? node.update.map(e => this.generate(e)).join(', ') : this.generate(node.update)) : ''
2662
- return `${this.indent()}for (${init}; ${test}; ${update}) ${this.generateBlock(node.body)}`
2663
- }
2664
-
2665
- generateForeachStatement(node) {
2666
- let result = `${this.indent()}foreach (${this.generate(node.source)} as `
2667
- if (node.key) {
2668
- result += `${this.generate(node.key)} => `
2669
- }
2670
- if (node.byRef) result += '&'
2671
- result += `${this.generate(node.value)}) ${this.generateBlock(node.body)}`
2672
- return result
2673
- }
2674
-
2675
- generateWhileStatement(node) {
2676
- return `${this.indent()}while (${this.generate(node.test)}) ${this.generateBlock(node.body)}`
2677
- }
2678
-
2679
- generateDoWhileStatement(node) {
2680
- return `${this.indent()}do ${this.generateBlock(node.body)} while (${this.generate(node.test)});`
2681
- }
2682
-
2683
- generateBreakStatement(node) {
2684
- return `${this.indent()}break${node.level ? ' ' + node.level : ''};`
2685
- }
2686
-
2687
- generateContinueStatement(node) {
2688
- return `${this.indent()}continue${node.level ? ' ' + node.level : ''};`
2689
- }
2690
-
2691
- generateReturnStatement(node) {
2692
- return `${this.indent()}return${node.argument ? ' ' + this.generate(node.argument) : ''};`
2693
- }
2694
-
2695
- generateThrowStatement(node) {
2696
- return `${this.indent()}throw ${this.generate(node.argument)};`
2697
- }
2698
-
2699
- generateTryStatement(node) {
2700
- let result = `${this.indent()}try ${this.generate(node.block)}`
2701
- for (const c of node.catches) {
2702
- result += ' ' + this.generate(c)
2703
- }
2704
- if (node.finalizer) {
2705
- result += ' ' + this.generate(node.finalizer)
2706
- }
2707
- return result
2708
- }
2709
-
2710
- generateCatchClause(node) {
2711
- const types = node.types.map(t => this.generate(t)).join('|')
2712
- const param = node.param ? ' $' + node.param : ''
2713
- return `catch (${types}${param}) ${this.generate(node.body)}`
2714
- }
2715
-
2716
- generateFinallyClause(node) {
2717
- return `finally ${this.generate(node.body)}`
2718
- }
2719
-
2720
- generateFunctionDeclaration(node) {
2721
- let result = `${this.indent()}function `
2722
- if (node.byRef) result += '&'
2723
- result += `${node.name}(`
2724
- result += node.params.map(p => this.generate(p)).join(', ')
2725
- result += ')'
2726
- if (node.returnType) result += ': ' + this.generate(node.returnType)
2727
- result += ' ' + this.generate(node.body)
2728
- return result
2729
- }
2730
-
2731
- generateParameter(node) {
2732
- let result = ''
2733
- if (node.visibility) result += node.visibility + ' '
2734
- if (node.readonly) result += 'readonly '
2735
- if (node.paramType) result += this.generate(node.paramType) + ' '
2736
- if (node.byRef) result += '&'
2737
- if (node.variadic) result += '...'
2738
- result += '$' + node.name
2739
- if (node.default) result += ' = ' + this.generate(node.default)
2740
- return result
2741
- }
2742
-
2743
- generateTypeReference(node) {
2744
- return (node.nullable ? '?' : '') + node.name
2745
- }
2746
-
2747
- generateUnionType(node) {
2748
- return node.types.map(t => this.generate(t)).join('|')
2749
- }
2750
-
2751
- generateCallExpression(node) {
2752
- const callee = this.generate(node.callee)
2753
- const args = node.arguments.map(a => this.generate(a)).join(', ')
2754
- return `${callee}(${args})`
2755
- }
2756
-
2757
- generateNamedArgument(node) {
2758
- return `${node.name}: ${this.generate(node.value)}`
2759
- }
2760
-
2761
- generateNewExpression(node) {
2762
- const callee = this.generate(node.callee)
2763
- const args = node.arguments.length ? '(' + node.arguments.map(a => this.generate(a)).join(', ') + ')' : ''
2764
- return `new ${callee}${args}`
2765
- }
2766
-
2767
- generateMemberExpression(node) {
2768
- const op = node.nullsafe ? '?->' : '->'
2769
- return `${this.generate(node.object)}${op}${this.generate(node.property)}`
2770
- }
2771
-
2772
- generateStaticMemberExpression(node) {
2773
- return `${this.generate(node.object)}::${this.generate(node.property)}`
2774
- }
2775
-
2776
- generateArrayAccess(node) {
2777
- const index = node.index ? this.generate(node.index) : ''
2778
- return `${this.generate(node.array)}[${index}]`
2779
- }
2780
-
2781
- generateClassDeclaration(node) {
2782
- let result = this.indent()
2783
- if (node.modifiers.length) result += node.modifiers.join(' ') + ' '
2784
- result += `class ${node.name}`
2785
- if (node.superClass) result += ` extends ${this.generate(node.superClass)}`
2786
- if (node.interfaces.length) result += ` implements ${node.interfaces.map(i => this.generate(i)).join(', ')}`
2787
- result += ' ' + this.generate(node.body)
2788
- return result
2789
- }
2790
-
2791
- generateClassBody(node) {
2792
- this.indentLevel++
2793
- const members = node.members.map(m => this.generate(m)).join('\n\n')
2794
- this.indentLevel--
2795
- return `{\n${members}\n${this.indent()}}`
2796
- }
2797
-
2798
- generateMethodDefinition(node) {
2799
- let result = this.indent()
2800
- if (node.modifiers.length) result += node.modifiers.join(' ') + ' '
2801
- result += 'function '
2802
- if (node.byRef) result += '&'
2803
- result += `${node.name}(`
2804
- result += node.params.map(p => this.generate(p)).join(', ')
2805
- result += ')'
2806
- if (node.returnType) result += ': ' + this.generate(node.returnType)
2807
- if (node.body) {
2808
- result += ' ' + this.generate(node.body)
2809
- } else {
2810
- result += ';'
2811
- }
2812
- return result
2813
- }
2814
-
2815
- generatePropertyDeclaration(node) {
2816
- let result = this.indent()
2817
- if (node.modifiers.length) result += node.modifiers.join(' ') + ' '
2818
- if (node.propertyType) result += this.generate(node.propertyType) + ' '
2819
- result += '$' + node.name
2820
- if (node.value) result += ' = ' + this.generate(node.value)
2821
- result += ';'
2822
- return result
2823
- }
2824
-
2825
- generateClassConstant(node) {
2826
- let result = this.indent()
2827
- if (node.modifiers.length) result += node.modifiers.join(' ') + ' '
2828
- result += 'const '
2829
- if (node.constType) result += this.generate(node.constType) + ' '
2830
- result += `${node.name} = ${this.generate(node.value)};`
2831
- return result
2832
- }
2833
-
2834
- generateInterfaceDeclaration(node) {
2835
- let result = `${this.indent()}interface ${node.name}`
2836
- if (node.extends.length) result += ` extends ${node.extends.map(e => this.generate(e)).join(', ')}`
2837
- result += ' ' + this.generate(node.body)
2838
- return result
2839
- }
2840
-
2841
- generateTraitDeclaration(node) {
2842
- return `${this.indent()}trait ${node.name} ${this.generate(node.body)}`
2843
- }
2844
-
2845
- generateTraitUse(node) {
2846
- return `${this.indent()}use ${node.traits.map(t => this.generate(t)).join(', ')};`
2847
- }
2848
-
2849
- generateEnumDeclaration(node) {
2850
- let result = `${this.indent()}enum ${node.name}`
2851
- if (node.backingType) result += ': ' + this.generate(node.backingType)
2852
- if (node.interfaces.length) result += ` implements ${node.interfaces.map(i => this.generate(i)).join(', ')}`
2853
- result += ' {\n'
2854
- this.indentLevel++
2855
- for (const item of node.body) {
2856
- result += this.generate(item) + '\n'
2857
- }
2858
- this.indentLevel--
2859
- result += `${this.indent()}}`
2860
- return result
2861
- }
2862
-
2863
- generateEnumCase(node) {
2864
- let result = `${this.indent()}case ${node.name}`
2865
- if (node.value) result += ' = ' + this.generate(node.value)
2866
- result += ';'
2867
- return result
2868
- }
2869
-
2870
- generateNamespaceDeclaration(node) {
2871
- let result = `${this.indent()}namespace`
2872
- if (node.name) result += ' ' + this.generate(node.name)
2873
- if (node.body) {
2874
- result += ' {\n'
2875
- this.indentLevel++
2876
- result += node.body.map(s => this.generate(s)).join('\n')
2877
- this.indentLevel--
2878
- result += `\n${this.indent()}}`
2879
- } else {
2880
- result += ';'
2881
- }
2882
- return result
2883
- }
2884
-
2885
- generateUseDeclaration(node) {
2886
- let result = `${this.indent()}use `
2887
- if (node.useType) result += node.useType + ' '
2888
- result += node.declarations.map(d => this.generate(d)).join(', ')
2889
- result += ';'
2890
- return result
2891
- }
2892
-
2893
- generateUseClause(node) {
2894
- let result = this.generate(node.name)
2895
- if (node.alias) result += ' as ' + node.alias
2896
- return result
2897
- }
2898
-
2899
- generateNamespaceName(node) {
2900
- return (node.absolute ? '\\' : '') + node.parts.join('\\')
2901
- }
2902
-
2903
- generateIncludeExpression(node) {
2904
- const keyword = node.require
2905
- ? (node.once ? 'require_once' : 'require')
2906
- : (node.once ? 'include_once' : 'include')
2907
- return `${keyword} ${this.generate(node.path)}`
2908
- }
2909
-
2910
- generateExpressionStatement(node) {
2911
- return `${this.indent()}${this.generate(node.expression)};`
2912
- }
2913
-
2914
- generateEmptyStatement() {
2915
- return `${this.indent()};`
2916
- }
2917
-
2918
- generateGlobalStatement(node) {
2919
- return `${this.indent()}global ${node.variables.map(v => this.generate(v)).join(', ')};`
2920
- }
2921
-
2922
- generateStaticStatement(node) {
2923
- const decls = node.declarations.map(d => {
2924
- let result = '$' + d.name
2925
- if (d.value) result += ' = ' + this.generate(d.value)
2926
- return result
2927
- }).join(', ')
2928
- return `${this.indent()}static ${decls};`
2929
- }
2930
-
2931
- generateClosureExpression(node) {
2932
- let result = node.static ? 'static ' : ''
2933
- result += 'function ('
2934
- result += node.params.map(p => this.generate(p)).join(', ')
2935
- result += ')'
2936
- if (node.uses.length) {
2937
- result += ' use ('
2938
- result += node.uses.map(u => (u.byRef ? '&$' : '$') + u.variable).join(', ')
2939
- result += ')'
2940
- }
2941
- if (node.returnType) result += ': ' + this.generate(node.returnType)
2942
- result += ' ' + this.generate(node.body)
2943
- return result
2944
- }
2945
-
2946
- generateArrowFunction(node) {
2947
- let result = node.static ? 'static ' : ''
2948
- result += 'fn('
2949
- result += node.params.map(p => this.generate(p)).join(', ')
2950
- result += ')'
2951
- if (node.returnType) result += ': ' + this.generate(node.returnType)
2952
- result += ' => ' + this.generate(node.body)
2953
- return result
2954
- }
2955
-
2956
- generateListExpression(node) {
2957
- const elements = node.elements.map(e => e ? this.generate(e) : '').join(', ')
2958
- return `list(${elements})`
2959
- }
2960
-
2961
- generateYieldExpression(node) {
2962
- if (node.key) {
2963
- return `yield ${this.generate(node.key)} => ${this.generate(node.value)}`
2964
- }
2965
- if (node.value) {
2966
- return `yield ${this.generate(node.value)}`
2967
- }
2968
- return 'yield'
2969
- }
2970
-
2971
- generateYieldFromExpression(node) {
2972
- return `yield from ${this.generate(node.expression)}`
2973
- }
2974
-
2975
- generateThrowExpression(node) {
2976
- return `throw ${this.generate(node.argument)}`
2977
- }
2978
-
2979
- generateExitExpression(node) {
2980
- const keyword = node.isDie ? 'die' : 'exit'
2981
- if (node.argument) {
2982
- return `${keyword}(${this.generate(node.argument)})`
2983
- }
2984
- return keyword
2985
- }
2986
-
2987
- generateEmptyExpression(node) {
2988
- return `empty(${this.generate(node.argument)})`
2989
- }
2990
-
2991
- generateIssetExpression(node) {
2992
- return `isset(${node.arguments.map(a => this.generate(a)).join(', ')})`
2993
- }
2994
-
2995
- generateUnsetStatement(node) {
2996
- return `${this.indent()}unset(${node.arguments.map(a => this.generate(a)).join(', ')});`
2997
- }
2998
-
2999
- generateEvalExpression(node) {
3000
- return `eval(${this.generate(node.code)})`
3001
- }
3002
-
3003
- generatePrintExpression(node) {
3004
- return `print ${this.generate(node.argument)}`
3005
- }
3006
-
3007
- generateShellExecExpression(node) {
3008
- return '`' + node.command + '`'
3009
- }
3010
-
3011
- generateAnonymousClass(node) {
3012
- let result = 'new class'
3013
- if (node.arguments.length) {
3014
- result += '(' + node.arguments.map(a => this.generate(a)).join(', ') + ')'
3015
- }
3016
- if (node.superClass) result += ` extends ${this.generate(node.superClass)}`
3017
- if (node.interfaces.length) result += ` implements ${node.interfaces.map(i => this.generate(i)).join(', ')}`
3018
- result += ' ' + this.generate(node.body)
3019
- return result
3020
- }
3021
-
3022
- generateLabelStatement(node) {
3023
- return `${node.name}:`
3024
- }
3025
-
3026
- generateGotoStatement(node) {
3027
- return `${this.indent()}goto ${node.label};`
3028
- }
3029
- }
3030
-
3031
- module.exports = {
3032
- PHPLexer,
3033
- PHPParser,
3034
- PHPCodeGenerator,
3035
- PHP_KEYWORDS,
3036
- PHP_TYPES
3037
- }