onelaraveljs 1.0.0 → 1.1.1

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 (119) hide show
  1. package/README.md +1 -1
  2. package/bin/onejs-build.js +32 -0
  3. package/package.json +11 -3
  4. package/scripts/README-template-compiler.md +133 -0
  5. package/scripts/README.md +61 -0
  6. package/scripts/__pycache__/build.cpython-314.pyc +0 -0
  7. package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
  8. package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
  9. package/scripts/build.py +573 -0
  10. package/scripts/check-system-errors.php +214 -0
  11. package/scripts/compile.py +101 -0
  12. package/scripts/compiler/README_CONFIG.md +196 -0
  13. package/scripts/compiler/__init__.py +18 -0
  14. package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
  16. package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
  17. package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
  18. package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
  19. package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
  20. package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
  21. package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
  22. package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
  23. package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
  24. package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
  25. package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
  26. package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
  27. package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
  28. package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
  29. package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
  30. package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
  31. package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
  32. package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
  33. package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
  34. package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
  35. package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
  36. package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
  37. package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
  38. package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
  39. package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
  40. package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
  41. package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
  42. package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
  43. package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
  44. package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
  45. package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
  46. package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
  47. package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
  48. package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
  49. package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
  50. package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
  51. package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
  52. package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
  53. package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
  54. package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
  55. package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
  56. package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
  57. package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
  58. package/scripts/compiler/binding_directive_service.py +103 -0
  59. package/scripts/compiler/class_binding_handler.py +347 -0
  60. package/scripts/compiler/cli.py +34 -0
  61. package/scripts/compiler/code_generator.py +141 -0
  62. package/scripts/compiler/compiler.config.json +36 -0
  63. package/scripts/compiler/compiler_utils.py +55 -0
  64. package/scripts/compiler/conditional_handlers.py +252 -0
  65. package/scripts/compiler/config.py +107 -0
  66. package/scripts/compiler/declaration_tracker.py +420 -0
  67. package/scripts/compiler/directive_processors.py +603 -0
  68. package/scripts/compiler/echo_processor.py +667 -0
  69. package/scripts/compiler/event_directive_processor.py +1099 -0
  70. package/scripts/compiler/fetch_parser.py +49 -0
  71. package/scripts/compiler/function_generators.py +310 -0
  72. package/scripts/compiler/loop_handlers.py +224 -0
  73. package/scripts/compiler/main_compiler.py +1763 -0
  74. package/scripts/compiler/parsers.py +1418 -0
  75. package/scripts/compiler/php_converter.py +470 -0
  76. package/scripts/compiler/php_js_converter.py +603 -0
  77. package/scripts/compiler/register_parser.py +480 -0
  78. package/scripts/compiler/section_handlers.py +122 -0
  79. package/scripts/compiler/show_directive_handler.py +85 -0
  80. package/scripts/compiler/style_directive_handler.py +169 -0
  81. package/scripts/compiler/template_analyzer.py +162 -0
  82. package/scripts/compiler/template_processor.py +1167 -0
  83. package/scripts/compiler/template_processors.py +1557 -0
  84. package/scripts/compiler/test_compiler.py +69 -0
  85. package/scripts/compiler/utils.py +54 -0
  86. package/scripts/compiler/variables_analyzer.py +135 -0
  87. package/scripts/compiler/view_identifier_generator.py +278 -0
  88. package/scripts/compiler/wrapper_parser.py +78 -0
  89. package/scripts/dev-context.js +311 -0
  90. package/scripts/dev.js +109 -0
  91. package/scripts/generate-assets-order.js +200 -0
  92. package/scripts/migrate-namespace.php +146 -0
  93. package/scripts/node/MIGRATION.md +190 -0
  94. package/scripts/node/README.md +269 -0
  95. package/scripts/node/build.js +208 -0
  96. package/scripts/node/compiler/compiler-utils.js +38 -0
  97. package/scripts/node/compiler/conditional-handlers.js +45 -0
  98. package/scripts/node/compiler/config.js +178 -0
  99. package/scripts/node/compiler/directive-processors.js +51 -0
  100. package/scripts/node/compiler/event-directive-processor.js +182 -0
  101. package/scripts/node/compiler/function-generators.js +239 -0
  102. package/scripts/node/compiler/loop-handlers.js +45 -0
  103. package/scripts/node/compiler/main-compiler.js +236 -0
  104. package/scripts/node/compiler/parsers.js +358 -0
  105. package/scripts/node/compiler/php-converter.js +227 -0
  106. package/scripts/node/compiler/register-parser.js +32 -0
  107. package/scripts/node/compiler/section-handlers.js +46 -0
  108. package/scripts/node/compiler/template-analyzer.js +50 -0
  109. package/scripts/node/compiler/template-processor.js +371 -0
  110. package/scripts/node/compiler/template-processors.js +219 -0
  111. package/scripts/node/compiler/utils.js +203 -0
  112. package/scripts/node/compiler/wrapper-parser.js +25 -0
  113. package/scripts/node/package.json +24 -0
  114. package/scripts/node/test-compiler.js +52 -0
  115. package/scripts/node-run.cjs +28 -0
  116. package/scripts/standardize-directories.php +92 -0
  117. package/templates/view.module.js +2 -0
  118. package/templates/view.tpl-raw.js +13 -0
  119. package/templates/wraper.js +71 -0
@@ -0,0 +1,470 @@
1
+ """
2
+ Chuyển đổi các biểu thức PHP sang JavaScript
3
+ """
4
+
5
+ from config import JS_FUNCTION_PREFIX
6
+ from utils import normalize_quotes
7
+ from php_js_converter import php_to_js_advanced
8
+ import re
9
+ import subprocess
10
+
11
+ def convert_php_array_with_php_r(php_array_expr):
12
+ """Convert PHP array to JSON using php -r command"""
13
+ try:
14
+ # Tạo PHP code để convert array sang JSON
15
+ php_code = f"try {{ echo json_encode({php_array_expr}); }} catch (Exception $e) {{ echo '[]'; }}"
16
+
17
+ # Chạy php -r command
18
+ result = subprocess.run(
19
+ ['php', '-r', php_code],
20
+ capture_output=True,
21
+ text=True,
22
+ timeout=5, # 5 giây timeout
23
+ check=False # Không raise exception khi return code != 0
24
+ )
25
+
26
+ if result.returncode == 0:
27
+ # Parse JSON output
28
+ json_output = result.stdout.strip()
29
+ return json_output
30
+ else:
31
+ print(f"PHP error: {result.stderr}")
32
+ return None
33
+
34
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError) as e:
35
+ print(f"Error running php -r: {e}")
36
+ return None
37
+
38
+ def convert_php_array_to_json(expr):
39
+ """Convert PHP array syntax to JSON object/array syntax using php -r"""
40
+ if not expr or '[' not in expr:
41
+ return expr
42
+
43
+ # Tìm tất cả các mảng PHP và convert chúng
44
+ def replace_array(match):
45
+ full_array = match.group(0) # Toàn bộ array expression
46
+ inner = match.group(1).strip()
47
+
48
+ if not inner:
49
+ return '[]'
50
+
51
+ # Check if this is array access vs array literal
52
+ if (("'" in inner and inner.count("'") == 2 and not '=>' in inner) or
53
+ ('"' in inner and inner.count('"') == 2 and not '=>' in inner) or
54
+ (inner.replace('_', '').replace('.', '').isalnum() and not '=>' in inner)):
55
+ return '[' + inner + ']' # Keep as array access
56
+
57
+ # Sử dụng php -r để convert array
58
+ json_result = convert_php_array_with_php_r(full_array)
59
+ if json_result is not None:
60
+ return json_result
61
+
62
+ # Fallback to old method if php -r fails
63
+ return _convert_php_array_legacy(full_array)
64
+
65
+ # Thử sử dụng php -r cho toàn bộ expression trước
66
+ json_result = convert_php_array_with_php_r(expr)
67
+ if json_result is not None and 'PHP error' not in str(json_result):
68
+ return json_result
69
+
70
+ # Fallback: chỉ xử lý quotes đơn giản
71
+ expr = re.sub(r"'([^']*)'", r'"\1"', expr)
72
+ return expr
73
+
74
+ def _process_array_content(inner_content):
75
+ """Process array content directly without recursion"""
76
+ if not inner_content:
77
+ return '[]'
78
+
79
+ # Process array elements
80
+ elements = []
81
+ current_element = ''
82
+ paren_count = 0
83
+ bracket_count = 0
84
+ in_quotes = False
85
+ quote_char = ''
86
+
87
+ i = 0
88
+ while i < len(inner_content):
89
+ char = inner_content[i]
90
+
91
+ if not in_quotes:
92
+ if char in ['"', "'"]:
93
+ in_quotes = True
94
+ quote_char = char
95
+ current_element += char
96
+ elif char == '(':
97
+ paren_count += 1
98
+ current_element += char
99
+ elif char == ')':
100
+ paren_count -= 1
101
+ current_element += char
102
+ elif char == '[':
103
+ bracket_count += 1
104
+ current_element += char
105
+ elif char == ']':
106
+ bracket_count -= 1
107
+ current_element += char
108
+ elif char == ',' and paren_count == 0 and bracket_count == 0:
109
+ # End of element
110
+ elements.append(current_element.strip())
111
+ current_element = ''
112
+ else:
113
+ current_element += char
114
+ else:
115
+ if char == quote_char:
116
+ # Check if it's escaped
117
+ escape_count = 0
118
+ j = i - 1
119
+ while j >= 0 and inner_content[j] == '\\':
120
+ escape_count += 1
121
+ j -= 1
122
+
123
+ # If even number of backslashes, quote is not escaped
124
+ if escape_count % 2 == 0:
125
+ in_quotes = False
126
+
127
+ current_element += char
128
+
129
+ i += 1
130
+
131
+ # Add last element
132
+ if current_element.strip():
133
+ elements.append(current_element.strip())
134
+
135
+ # Process elements
136
+ processed_elements = []
137
+ for element in elements:
138
+ element = element.strip()
139
+ if not element:
140
+ continue
141
+
142
+ # Check if it's a key => value pair
143
+ if '=>' in element:
144
+ key, value = element.split('=>', 1)
145
+ key = key.strip()
146
+ value = value.strip()
147
+
148
+ # Convert key
149
+ if key.startswith("'") and key.endswith("'"):
150
+ key_js = '"' + key[1:-1] + '"'
151
+ elif key.startswith('"') and key.endswith('"'):
152
+ key_js = key
153
+ elif key.replace('_', '').replace('.', '').isalnum():
154
+ key_js = '"' + key + '"'
155
+ else:
156
+ key_js = key
157
+
158
+ # Convert value
159
+ if value.startswith("'") and value.endswith("'"):
160
+ value_js = '"' + value[1:-1] + '"'
161
+ elif value.startswith('"') and value.endswith('"'):
162
+ value_js = value
163
+ elif value in ['true', 'false', 'null']:
164
+ value_js = value
165
+ elif value.replace('.', '').replace('-', '').isdigit():
166
+ value_js = value
167
+ else:
168
+ # Try to convert nested arrays using php -r
169
+ try:
170
+ value_js = convert_php_array_with_php_r(value)
171
+ except:
172
+ value_js = value
173
+
174
+ processed_elements.append(key_js + ': ' + value_js)
175
+ else:
176
+ # Simple value
177
+ if element.startswith("'") and element.endswith("'"):
178
+ element_js = '"' + element[1:-1] + '"'
179
+ elif element.startswith('"') and element.endswith('"'):
180
+ element_js = element
181
+ elif element in ['true', 'false', 'null']:
182
+ element_js = element
183
+ elif element.replace('.', '').replace('-', '').isdigit():
184
+ element_js = element
185
+ else:
186
+ # Try to convert nested arrays using php -r
187
+ try:
188
+ element_js = convert_php_array_with_php_r(element)
189
+ except:
190
+ element_js = element
191
+
192
+ processed_elements.append(element_js)
193
+
194
+ if not processed_elements:
195
+ return '[]'
196
+ elif any(':' in elem for elem in processed_elements):
197
+ # This is a mixed array, treat as object
198
+ return '{' + ', '.join(processed_elements) + '}'
199
+ else:
200
+ return '[' + ', '.join(processed_elements) + ']'
201
+
202
+ def _convert_php_array_legacy(expr):
203
+ """Legacy method for converting PHP arrays (fallback)"""
204
+ # Find all array patterns and convert them
205
+ def replace_array(match):
206
+ inner = match.group(1).strip()
207
+ if not inner:
208
+ return '[]'
209
+
210
+ # Check if this is array access vs array literal (không phải nested array)
211
+ if (("'" in inner and inner.count("'") == 2 and not '=>' in inner and inner.count('[') == 0) or
212
+ ('"' in inner and inner.count('"') == 2 and not '=>' in inner and inner.count('[') == 0) or
213
+ (inner.replace('_', '').replace('.', '').isalnum() and not '=>' in inner and inner.count('[') == 0)):
214
+ return '[' + inner + ']' # Keep as array access
215
+
216
+ # Process array elements
217
+ elements = []
218
+ current_element = ''
219
+ paren_count = 0
220
+ bracket_count = 0
221
+ in_quotes = False
222
+ quote_char = ''
223
+ i = 0
224
+
225
+ while i < len(inner):
226
+ char = inner[i]
227
+
228
+ if (char == '"' or char == "'") and not in_quotes:
229
+ in_quotes = True
230
+ quote_char = char
231
+ elif char == quote_char and in_quotes:
232
+ in_quotes = False
233
+ quote_char = ''
234
+ elif not in_quotes:
235
+ if char == '(':
236
+ paren_count += 1
237
+ elif char == ')':
238
+ paren_count -= 1
239
+ elif char == '[':
240
+ bracket_count += 1
241
+ elif char == ']':
242
+ bracket_count -= 1
243
+ elif char == ',' and paren_count == 0 and bracket_count == 0:
244
+ elements.append(current_element.strip())
245
+ current_element = ''
246
+ i += 1
247
+ continue
248
+
249
+ current_element += char
250
+ i += 1
251
+
252
+ if current_element.strip():
253
+ elements.append(current_element.strip())
254
+
255
+ # Process each element
256
+ processed_elements = []
257
+ has_key_value_pairs = False
258
+
259
+ for element in elements:
260
+ element = element.strip()
261
+ if '=>' in element:
262
+ # This is a key => value pair
263
+ has_key_value_pairs = True
264
+ key_value = element.split('=>', 1)
265
+ key = key_value[0].strip().strip('\'"')
266
+ value = key_value[1].strip()
267
+
268
+ # Convert value to JS (recursively)
269
+ value_js = _convert_php_array_legacy(value)
270
+
271
+ # Normalize quotes in value_js
272
+ value_js = normalize_quotes(value_js)
273
+
274
+ # Handle key (add quotes if not already quoted)
275
+ if not (key.startswith('"') and key.endswith('"')) and not (key.startswith("'") and key.endswith("'")):
276
+ key = f'"{key}"'
277
+
278
+ processed_elements.append(f'{key}: {value_js}')
279
+ else:
280
+ # This is a simple value - check if it's a nested array
281
+ if '[' in element and ']' in element:
282
+ # It's a nested array, try php -r first
283
+ json_result = convert_php_array_with_php_r(element)
284
+ if json_result is not None:
285
+ value_js = json_result
286
+ else:
287
+ # Fallback to recursive conversion
288
+ value_js = _convert_php_array_legacy(element)
289
+ else:
290
+ # Simple value
291
+ value_js = element
292
+
293
+ value_js = normalize_quotes(value_js)
294
+ processed_elements.append(value_js)
295
+
296
+ # If contains key-value pairs, it's an object
297
+ if has_key_value_pairs:
298
+ return '{' + ', '.join(processed_elements) + '}'
299
+ else:
300
+ # Check if this is a numeric array
301
+ is_numeric_array = True
302
+ for element in elements:
303
+ if '=>' in element:
304
+ is_numeric_array = False
305
+ break
306
+
307
+ if is_numeric_array:
308
+ return '[' + ', '.join(processed_elements) + ']'
309
+ else:
310
+ # This is a mixed array, treat as object
311
+ return '{' + ', '.join(processed_elements) + '}'
312
+
313
+ # Apply array conversion recursively với balanced bracket matching
314
+ def find_and_replace_arrays(text):
315
+ result = text
316
+ while '[' in result:
317
+ # Tìm mảng đầu tiên
318
+ start = result.find('[')
319
+ if start == -1:
320
+ break
321
+
322
+ # Tìm closing bracket tương ứng với balanced matching
323
+ bracket_count = 0
324
+ end = start
325
+ in_quotes = False
326
+ quote_char = ''
327
+
328
+ for i in range(start, len(result)):
329
+ char = result[i]
330
+
331
+ if not in_quotes:
332
+ if char in ['"', "'"]:
333
+ in_quotes = True
334
+ quote_char = char
335
+ elif char == '[':
336
+ bracket_count += 1
337
+ elif char == ']':
338
+ bracket_count -= 1
339
+ if bracket_count == 0:
340
+ end = i
341
+ break
342
+ else:
343
+ if char == quote_char:
344
+ # Check if it's escaped
345
+ escape_count = 0
346
+ j = i - 1
347
+ while j >= 0 and result[j] == '\\':
348
+ escape_count += 1
349
+ j -= 1
350
+
351
+ # If even number of backslashes, quote is not escaped
352
+ if escape_count % 2 == 0:
353
+ in_quotes = False
354
+
355
+ if bracket_count == 0:
356
+ # Tìm thấy mảng hoàn chỉnh
357
+ array_expr = result[start:end+1]
358
+ inner_content = array_expr[1:-1].strip()
359
+
360
+ if not inner_content:
361
+ replacement = '[]'
362
+ else:
363
+ # Check if this is array access vs array literal
364
+ if (("'" in inner_content and inner_content.count("'") == 2 and not '=>' in inner_content and inner_content.count('[') == 0) or
365
+ ('"' in inner_content and inner_content.count('"') == 2 and not '=>' in inner_content and inner_content.count('[') == 0) or
366
+ (inner_content.replace('_', '').replace('.', '').isalnum() and not '=>' in inner_content and inner_content.count('[') == 0)):
367
+ replacement = array_expr # Keep as array access
368
+ else:
369
+ # Process as array literal - xử lý trực tiếp để tránh vòng lặp
370
+ replacement = _process_array_content(inner_content)
371
+
372
+ result = result[:start] + replacement + result[end+1:]
373
+ else:
374
+ break
375
+
376
+ return result
377
+
378
+ expr = find_and_replace_arrays(expr)
379
+
380
+ return expr
381
+
382
+ def php_to_js(expr):
383
+ """Convert PHP expression to JavaScript using advanced converter"""
384
+ if expr is None:
385
+ return "''"
386
+
387
+ # Remove PHP closure use(...) syntax
388
+ expr = re.sub(r'\s+use\s*\([^)]*\)', '', expr)
389
+
390
+ # Handle foreach BEFORE converting => to :
391
+ foreach_pattern = r'\bforeach\s*\(\s*(.*?)\s*as\s*\$?(\w+)(\s*=>\s*\$?(\w+))?\s*\)(\s*)\{'
392
+
393
+ def replace_foreach(match):
394
+ array_expr = match.group(1).strip()
395
+ first_var = match.group(2)
396
+ space_before_brace = match.group(5) if len(match.groups()) >= 5 else ' '
397
+
398
+ if match.group(3): # Has key => value
399
+ key_var = first_var # first var is key
400
+ value_var = match.group(4) # second var is value
401
+ return f'{JS_FUNCTION_PREFIX}.foreach({array_expr}, ({value_var}, {key_var}, __loopIndex, loop) =>{space_before_brace}{{'
402
+ else: # Only value
403
+ value_var = first_var
404
+ return f'{JS_FUNCTION_PREFIX}.foreach({array_expr}, ({value_var}, __loopKey, __loopIndex, loop) =>{space_before_brace}{{'
405
+
406
+ expr = re.sub(foreach_pattern, replace_foreach, expr)
407
+
408
+ # Use advanced converter for complex structures
409
+ expr = php_to_js_advanced(expr)
410
+
411
+ return expr
412
+
413
+ def convert_php_to_js(php_expr):
414
+ """Convert PHP expressions to JavaScript equivalents"""
415
+ php_expr = php_expr.strip()
416
+
417
+ # Convert PHP operators to JavaScript with correct precedence order:
418
+ # (1) String concatenation "." -> "+"
419
+ # (2) Object accessor "->" -> "."
420
+ # (3) Static accessor "::" -> "."
421
+
422
+ # Step 1: Convert string concatenation (.) to (+) but be careful with object access and floats
423
+ # Don't convert dots that are part of numbers or already converted object access
424
+ php_expr = convert_string_concatenation(php_expr)
425
+
426
+ # Step 2: Convert object accessor (->) to (.)
427
+ php_expr = re.sub(r'->', '.', php_expr)
428
+
429
+ # Step 3: Convert static accessor (::) to (.)
430
+ # Handle $Class::$property and Class::method patterns
431
+ php_expr = re.sub(r'::', '.', php_expr)
432
+
433
+ # Remove $ prefix from variables (but keep it in template strings)
434
+ php_expr = convert_php_variables(php_expr)
435
+
436
+ return php_expr
437
+
438
+ def convert_string_concatenation(php_expr):
439
+ """Convert PHP string concatenation (.) to JavaScript (+) with proper precedence"""
440
+ # Protect string literals first
441
+ string_literals = []
442
+ def protect_string_literal(match):
443
+ pattern = match.group(0)
444
+ placeholder = f"__STR_LIT_{len(string_literals)}__"
445
+ string_literals.append(pattern)
446
+ return placeholder
447
+
448
+ # Protect single-quoted strings
449
+ php_expr = re.sub(r"'[^']*'", protect_string_literal, php_expr)
450
+ # Protect double-quoted strings
451
+ php_expr = re.sub(r'"[^"]*"', protect_string_literal, php_expr)
452
+
453
+ # Pattern to match string concatenation dots (not in numbers, not in decimals)
454
+ # Look for dots that are surrounded by non-digit characters or are at word boundaries
455
+ pattern = r'(\w|\]|\))\s*\.\s*(?=\w|\$|\'|"|\()'
456
+
457
+ result = re.sub(pattern, r'\1+', php_expr)
458
+
459
+ # Restore string literals
460
+ for i, literal in enumerate(string_literals):
461
+ result = result.replace(f"__STR_LIT_{i}__", literal)
462
+
463
+ return result
464
+
465
+ def convert_php_variables(php_expr):
466
+ """Convert $variable to variable (remove $ prefix)"""
467
+ # Don't convert $ in template strings (between backticks)
468
+ # Simple approach: convert $word patterns that are not in template strings
469
+ result = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'\1', php_expr)
470
+ return result