onelaraveljs 1.0.0 → 1.1.3

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 (121) hide show
  1. package/README.md +1 -1
  2. package/bin/onejs-build.js +32 -0
  3. package/index.js +3 -1
  4. package/package.json +11 -3
  5. package/scripts/README-template-compiler.md +133 -0
  6. package/scripts/README.md +61 -0
  7. package/scripts/__pycache__/build.cpython-314.pyc +0 -0
  8. package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
  9. package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
  10. package/scripts/build.py +574 -0
  11. package/scripts/check-system-errors.php +214 -0
  12. package/scripts/compile.py +101 -0
  13. package/scripts/compiler/README_CONFIG.md +196 -0
  14. package/scripts/compiler/__init__.py +18 -0
  15. package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
  16. package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
  17. package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
  18. package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
  19. package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
  20. package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
  21. package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
  22. package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
  23. package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
  24. package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
  25. package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
  26. package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
  27. package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
  28. package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
  29. package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
  30. package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
  31. package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
  32. package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
  33. package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
  34. package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
  35. package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
  36. package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
  37. package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
  38. package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
  39. package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
  40. package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
  41. package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
  42. package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
  43. package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
  44. package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
  45. package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
  46. package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
  47. package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
  48. package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
  49. package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
  50. package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
  51. package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
  52. package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
  53. package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
  54. package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
  55. package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
  56. package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
  57. package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
  58. package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
  59. package/scripts/compiler/binding_directive_service.py +103 -0
  60. package/scripts/compiler/class_binding_handler.py +347 -0
  61. package/scripts/compiler/cli.py +34 -0
  62. package/scripts/compiler/code_generator.py +141 -0
  63. package/scripts/compiler/compiler.config.json +36 -0
  64. package/scripts/compiler/compiler_utils.py +55 -0
  65. package/scripts/compiler/conditional_handlers.py +252 -0
  66. package/scripts/compiler/config.py +107 -0
  67. package/scripts/compiler/declaration_tracker.py +420 -0
  68. package/scripts/compiler/directive_processors.py +603 -0
  69. package/scripts/compiler/echo_processor.py +667 -0
  70. package/scripts/compiler/event_directive_processor.py +1099 -0
  71. package/scripts/compiler/fetch_parser.py +49 -0
  72. package/scripts/compiler/function_generators.py +310 -0
  73. package/scripts/compiler/loop_handlers.py +224 -0
  74. package/scripts/compiler/main_compiler.py +1763 -0
  75. package/scripts/compiler/parsers.py +1418 -0
  76. package/scripts/compiler/php_converter.py +470 -0
  77. package/scripts/compiler/php_js_converter.py +603 -0
  78. package/scripts/compiler/register_parser.py +480 -0
  79. package/scripts/compiler/section_handlers.py +122 -0
  80. package/scripts/compiler/show_directive_handler.py +85 -0
  81. package/scripts/compiler/style_directive_handler.py +169 -0
  82. package/scripts/compiler/template_analyzer.py +162 -0
  83. package/scripts/compiler/template_processor.py +1167 -0
  84. package/scripts/compiler/template_processors.py +1557 -0
  85. package/scripts/compiler/test_compiler.py +69 -0
  86. package/scripts/compiler/utils.py +54 -0
  87. package/scripts/compiler/variables_analyzer.py +135 -0
  88. package/scripts/compiler/view_identifier_generator.py +278 -0
  89. package/scripts/compiler/wrapper_parser.py +78 -0
  90. package/scripts/dev-context.js +311 -0
  91. package/scripts/dev.js +109 -0
  92. package/scripts/generate-assets-order.js +208 -0
  93. package/scripts/migrate-namespace.php +146 -0
  94. package/scripts/node/MIGRATION.md +190 -0
  95. package/scripts/node/README.md +269 -0
  96. package/scripts/node/build.js +208 -0
  97. package/scripts/node/compiler/compiler-utils.js +38 -0
  98. package/scripts/node/compiler/conditional-handlers.js +45 -0
  99. package/scripts/node/compiler/config.js +178 -0
  100. package/scripts/node/compiler/directive-processors.js +51 -0
  101. package/scripts/node/compiler/event-directive-processor.js +182 -0
  102. package/scripts/node/compiler/function-generators.js +239 -0
  103. package/scripts/node/compiler/loop-handlers.js +45 -0
  104. package/scripts/node/compiler/main-compiler.js +236 -0
  105. package/scripts/node/compiler/parsers.js +358 -0
  106. package/scripts/node/compiler/php-converter.js +227 -0
  107. package/scripts/node/compiler/register-parser.js +32 -0
  108. package/scripts/node/compiler/section-handlers.js +46 -0
  109. package/scripts/node/compiler/template-analyzer.js +50 -0
  110. package/scripts/node/compiler/template-processor.js +371 -0
  111. package/scripts/node/compiler/template-processors.js +219 -0
  112. package/scripts/node/compiler/utils.js +203 -0
  113. package/scripts/node/compiler/wrapper-parser.js +25 -0
  114. package/scripts/node/package.json +24 -0
  115. package/scripts/node/test-compiler.js +52 -0
  116. package/scripts/node-run.cjs +28 -0
  117. package/scripts/standardize-directories.php +92 -0
  118. package/src/core/ViewManager.js +4 -4
  119. package/templates/view.module.js +2 -0
  120. package/templates/view.tpl-raw.js +13 -0
  121. package/templates/wraper.js +71 -0
@@ -0,0 +1,603 @@
1
+ """
2
+ PHP to JavaScript Converter - Advanced version for complex data structures
3
+ """
4
+
5
+ import re
6
+ from typing import List, Dict, Any, Tuple
7
+
8
+ class PHPToJSConverter:
9
+ """Advanced PHP to JavaScript converter for complex data structures"""
10
+
11
+ def __init__(self):
12
+ self.js_function_prefix = "App.View"
13
+
14
+ def convert_php_expression_to_js(self, expr: str) -> str:
15
+ """Convert PHP expression to JavaScript"""
16
+ if not expr or not expr.strip():
17
+ return "''"
18
+
19
+ expr = expr.strip()
20
+
21
+ # Protect ++ and -- operators
22
+ expr = expr.replace('++', '__INC_OPERATOR__')
23
+ expr = expr.replace('--', '__DEC_OPERATOR__')
24
+
25
+ # Step 1: Handle string concatenation (. to +) FIRST - but only for actual concatenation
26
+ # Skip function calls like route('api.users') and object access like now()->format()
27
+ # Also skip nested function calls like json_encode(event('view.rendered'))
28
+ if '.' in expr and ('$' in expr or '"' in expr or "'" in expr) and not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expr.strip()):
29
+ # Skip comparison operators like ===, ==, !=, !==, <, >, <=, >=
30
+ if re.search(r'[=!<>]=?', expr):
31
+ pass # Skip string concatenation for comparison operators
32
+ # Skip ternary operators like condition ? value1 : value2
33
+ elif re.search(r'\?.*:', expr):
34
+ pass # Skip string concatenation for ternary operators
35
+ # Check if this is object access pattern (var->method) - skip if so
36
+ # Pattern: anything->anything (not just $var->method)
37
+ elif not re.search(r'[a-zA-Z_][a-zA-Z0-9_]*->[a-zA-Z_][a-zA-Z0-9_]*', expr):
38
+ expr = self._handle_string_concatenation(expr)
39
+ # Restore ++ and -- operators
40
+ expr = expr.replace('__INC_OPERATOR__', '++')
41
+ expr = expr.replace('__DEC_OPERATOR__', '--')
42
+ return expr
43
+
44
+ # Step 2: Convert object property access (-> to .) AFTER string concatenation
45
+ expr = re.sub(r'->', '.', expr)
46
+
47
+ # Step 3: Handle PHP string concatenation with + operator
48
+ # Convert patterns like (+'string'+) to ('string')
49
+ expr = re.sub(r'\(\+([\'"][^\'\"]*[\'\"])\+\)', r'(\1)', expr)
50
+
51
+ # Fix patterns like config(+'app.debug'+) to config('app.debug')
52
+ expr = re.sub(r'(\w+)\(\+([\'"][^\'\"]*[\'\"])\+\)', r'\1(\2)', expr)
53
+
54
+ # Step 4: Handle patterns like +??+'string' FIRST
55
+ expr = re.sub(r'\+\?\?\+([\'"][^\'\"]*[\'\"])', r'??+\1', expr)
56
+
57
+ # Step 5: Handle PHP null coalescing operator ??
58
+ # Fix patterns like +??+ to ?? (remove extra +)
59
+ expr = re.sub(r'\+\?\?\+', '??', expr)
60
+ expr = re.sub(r'\?\?\+\+', '??', expr)
61
+
62
+ # Step 6: Handle patterns like +'string'+ (without parentheses) - but only for actual concatenation
63
+ # Don't convert function calls like route('api.users') -> route(+'api.users'+)
64
+ # Only convert when there's actual concatenation with variables
65
+ # Check if this is a function call pattern (function_name('string'))
66
+ if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)$', expr.strip()):
67
+ # Skip comparison operators like ===, ==, !=, !==, <, >, <=, >=
68
+ if not re.search(r'[=!<>]=?', expr):
69
+ # Skip ternary operators like condition ? value1 : value2
70
+ if not re.search(r'\?.*:', expr):
71
+ if '.' in expr and ('$' in expr or '+' in expr):
72
+ expr = re.sub(r'\+([\'"][^\'\"]*[\'\"])\+', r'+\1+', expr)
73
+
74
+ # Step 7: Handle double + operators
75
+ expr = re.sub(r'\+\+', '+', expr)
76
+
77
+ # Step 8: Fix ternary operators with + characters
78
+ # Fix patterns like condition ?+'value'+:'value' to condition ? 'value' : 'value'
79
+ expr = re.sub(r'\?\s*\+([\'"][^\'\"]*[\'\"])\+\s*:', r'? \1 :', expr)
80
+ expr = re.sub(r':\s*\+([\'"][^\'\"]*[\'\"])\+', r': \1', expr)
81
+
82
+ # Step 9: Fix remaining + characters in function calls and expressions
83
+ # Fix patterns like config(+'app.debug'+) to config('app.debug')
84
+ expr = re.sub(r'(\w+)\(\+([\'"][^\'\"]*[\'\"])\+\)', r'\1(\2)', expr)
85
+
86
+ # Fix nested function calls like json_encode(event(+'view.rendered'+))
87
+ # Recursively fix until no more patterns found
88
+ max_iterations = 10
89
+ for _ in range(max_iterations):
90
+ old_expr = expr
91
+ # Fix patterns like func(+'string'+) - match any characters after the closing )
92
+ expr = re.sub(r'(\w+)\(\+([\'"][^\'\"]*[\'\"])\+\)', r'\1(\2)', expr)
93
+ # Also fix patterns with extra characters after like func(+'string'+))
94
+ expr = re.sub(r'\(\+([\'"][^\'\"]*[\'\"])\+\)', r'(\1)', expr)
95
+ if old_expr == expr:
96
+ break
97
+
98
+ # Fix patterns like +'string'+ to 'string' (standalone)
99
+ expr = re.sub(r'\+([\'"][^\'\"]*[\'\"])\+', r'\1', expr)
100
+
101
+ # Remove PHP variable prefix, but preserve function calls
102
+ # Only remove $ from variable names, not from function calls
103
+ expr = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'\1', expr)
104
+
105
+ # Remove PHP (array) cast - not needed in JavaScript
106
+ expr = re.sub(r'\(array\)\s+', '', expr)
107
+
108
+ # Handle complex array/object structures first
109
+ expr = self._convert_complex_structures(expr)
110
+
111
+ # Handle string concatenation
112
+ expr = self._handle_string_concatenation(expr)
113
+
114
+ # Add function prefixes
115
+ expr = self._add_function_prefixes(expr)
116
+
117
+ # Restore ++ and -- operators
118
+ expr = expr.replace('__INC_OPERATOR__', '++')
119
+ expr = expr.replace('__DEC_OPERATOR__', '--')
120
+
121
+ return expr
122
+
123
+ def _convert_complex_structures(self, expr: str) -> str:
124
+ """Convert complex PHP structures (arrays, objects) to JavaScript"""
125
+
126
+ # Process from innermost brackets outward
127
+ max_iterations = 10 # Prevent infinite loops
128
+ iteration = 0
129
+
130
+ while '[' in expr and ']' in expr and iteration < max_iterations:
131
+ iteration += 1
132
+
133
+ # Find the innermost bracket pair
134
+ start_pos = -1
135
+ end_pos = -1
136
+ bracket_count = 0
137
+
138
+ for i, char in enumerate(expr):
139
+ if char == '[':
140
+ if start_pos == -1:
141
+ start_pos = i
142
+ bracket_count += 1
143
+ elif char == ']':
144
+ bracket_count -= 1
145
+ if bracket_count == 0:
146
+ end_pos = i
147
+ break
148
+
149
+ if start_pos != -1 and end_pos != -1:
150
+ array_content = expr[start_pos + 1:end_pos].strip()
151
+
152
+ # Check if this is array access vs array literal
153
+ if self._is_array_access(array_content):
154
+ # Skip this bracket pair
155
+ continue
156
+
157
+ # Convert array elements
158
+ elements = self._parse_array_elements(array_content)
159
+ js_elements = []
160
+
161
+ # Check if this is a mixed array (has both key=>value pairs and simple values)
162
+ has_key_value_pairs = any('=>' in elem for elem in elements)
163
+ has_simple_values = any('=>' not in elem for elem in elements)
164
+ is_mixed_array = has_key_value_pairs and has_simple_values
165
+
166
+ # Also check if this is an object array (only key=>value pairs)
167
+ is_object_array = has_key_value_pairs and not has_simple_values
168
+
169
+ if is_mixed_array:
170
+ # For mixed arrays, convert each element individually
171
+ for element in elements:
172
+ if '=>' in element:
173
+ # Key-value pair - check if it's a nested array
174
+ if element.startswith('[') and element.endswith(']'):
175
+ # This is a nested array with key-value pairs
176
+ nested_content = element[1:-1].strip()
177
+ nested_elements = self._parse_array_elements(nested_content)
178
+
179
+ # Check if all elements are key-value pairs
180
+ all_key_value = all('=>' in elem for elem in nested_elements)
181
+
182
+ if all_key_value:
183
+ # Convert to object
184
+ nested_object_parts = []
185
+ for nested_elem in nested_elements:
186
+ nested_js_element = self._convert_array_element(nested_elem)
187
+ nested_object_parts.append(nested_js_element)
188
+ js_elements.append('{' + ', '.join(nested_object_parts) + '}')
189
+ else:
190
+ # Keep as array
191
+ nested_js_elements = []
192
+ for nested_elem in nested_elements:
193
+ if '=>' in nested_elem:
194
+ # Key-value pair in nested array
195
+ nested_js_element = self._convert_array_element(nested_elem)
196
+ nested_js_elements.append('{' + nested_js_element + '}')
197
+ else:
198
+ # Simple value in nested array
199
+ nested_js_element = self._convert_value(nested_elem)
200
+ nested_js_elements.append(nested_js_element)
201
+ js_elements.append('[' + ', '.join(nested_js_elements) + ']')
202
+ else:
203
+ # Direct key-value pair
204
+ js_element = self._convert_array_element(element)
205
+ js_elements.append('{' + js_element + '}')
206
+ else:
207
+ # Simple value
208
+ js_element = self._convert_value(element)
209
+ js_elements.append(js_element)
210
+ elif is_object_array:
211
+ # For object arrays, check if we have nested arrays
212
+ has_nested_arrays = any(element.startswith('[') and element.endswith(']') for element in elements)
213
+
214
+ if has_nested_arrays:
215
+ # This is an object array with nested arrays - keep as array
216
+ for element in elements:
217
+ if '=>' in element:
218
+ # Key-value pair - check if it's a nested array
219
+ if element.startswith('[') and element.endswith(']'):
220
+ # This is a nested array with key-value pairs
221
+ nested_content = element[1:-1].strip()
222
+ nested_elements = self._parse_array_elements(nested_content)
223
+
224
+ # Check if all elements are key-value pairs
225
+ all_key_value = all('=>' in elem for elem in nested_elements)
226
+
227
+ if all_key_value:
228
+ # Convert to object
229
+ nested_object_parts = []
230
+ for nested_elem in nested_elements:
231
+ nested_js_element = self._convert_array_element(nested_elem)
232
+ nested_object_parts.append(nested_js_element)
233
+ js_elements.append('{' + ', '.join(nested_object_parts) + '}')
234
+ else:
235
+ # Keep as array
236
+ nested_js_elements = []
237
+ for nested_elem in nested_elements:
238
+ if '=>' in nested_elem:
239
+ # Key-value pair in nested array
240
+ nested_js_element = self._convert_array_element(nested_elem)
241
+ nested_js_elements.append('{' + nested_js_element + '}')
242
+ else:
243
+ # Simple value in nested array
244
+ nested_js_element = self._convert_value(nested_elem)
245
+ nested_js_elements.append(nested_js_element)
246
+ js_elements.append('[' + ', '.join(nested_js_elements) + ']')
247
+ else:
248
+ # Direct key-value pair
249
+ js_element = self._convert_array_element(element)
250
+ js_elements.append('{' + js_element + '}')
251
+ else:
252
+ # Simple value
253
+ js_element = self._convert_value(element)
254
+ js_elements.append(js_element)
255
+ else:
256
+ # Regular object array - combine all key-value pairs into single object
257
+ object_parts = []
258
+ for element in elements:
259
+ if '=>' in element:
260
+ # Key-value pair
261
+ js_element = self._convert_array_element(element)
262
+ object_parts.append(js_element)
263
+ # Combine into single object
264
+ new_content = '{' + ', '.join(object_parts) + '}'
265
+ expr = expr[:start_pos] + new_content + expr[end_pos + 1:]
266
+ continue
267
+ else:
268
+ # Regular array processing
269
+ for element in elements:
270
+ js_element = self._convert_array_element(element)
271
+ js_elements.append(js_element)
272
+
273
+ # Replace the bracket content
274
+ new_content = '[' + ', '.join(js_elements) + ']'
275
+ expr = expr[:start_pos] + new_content + expr[end_pos + 1:]
276
+ else:
277
+ break
278
+
279
+ return expr
280
+
281
+ def _is_array_access(self, content: str) -> bool:
282
+ """Check if this is array access (e.g., ['key']) vs array literal"""
283
+ # Simple heuristic: if it's a single quoted string or number, it's array access
284
+ content = content.strip()
285
+ if (content.startswith("'") and content.endswith("'") and content.count("'") == 2) or \
286
+ (content.startswith('"') and content.endswith('"') and content.count('"') == 2) or \
287
+ content.isdigit() or content.replace('.', '').isdigit():
288
+ return True
289
+ return False
290
+
291
+ def _parse_array_elements(self, content: str) -> List[str]:
292
+ """Parse array elements, handling nested structures"""
293
+ elements = []
294
+ current_element = ''
295
+ paren_count = 0
296
+ bracket_count = 0
297
+ in_quotes = False
298
+ quote_char = ''
299
+ i = 0
300
+
301
+ while i < len(content):
302
+ char = content[i]
303
+
304
+ if (char == '"' or char == "'") and not in_quotes:
305
+ in_quotes = True
306
+ quote_char = char
307
+ current_element += char
308
+ elif char == quote_char and in_quotes:
309
+ # Check if this is an escaped quote
310
+ if i > 0 and content[i - 1] == '\\':
311
+ current_element += char
312
+ else:
313
+ in_quotes = False
314
+ quote_char = ''
315
+ current_element += char
316
+ elif not in_quotes:
317
+ if char == '(':
318
+ paren_count += 1
319
+ current_element += char
320
+ elif char == ')':
321
+ paren_count -= 1
322
+ current_element += char
323
+ elif char == '[':
324
+ bracket_count += 1
325
+ current_element += char
326
+ elif char == ']':
327
+ bracket_count -= 1
328
+ current_element += char
329
+ elif char == ',' and paren_count == 0 and bracket_count == 0:
330
+ # This is a top-level comma
331
+ elements.append(current_element.strip())
332
+ current_element = ''
333
+ else:
334
+ current_element += char
335
+ else:
336
+ current_element += char
337
+
338
+ i += 1
339
+
340
+ # Add the last element
341
+ if current_element.strip():
342
+ elements.append(current_element.strip())
343
+
344
+ return elements
345
+
346
+ def _convert_array_element(self, element: str) -> str:
347
+ """Convert a single array element to JavaScript"""
348
+ element = element.strip()
349
+
350
+ # Check if it's a key => value pair
351
+ if '=>' in element:
352
+ key_value_parts = self._split_key_value(element)
353
+ if len(key_value_parts) == 2:
354
+ key, value = key_value_parts
355
+ js_key = self._convert_key(key)
356
+ js_value = self._convert_value(value)
357
+ return f'"{js_key}": {js_value}'
358
+
359
+ # It's a simple value
360
+ return self._convert_value(element)
361
+
362
+ def _split_key_value(self, element: str) -> List[str]:
363
+ """Split key => value pair, handling nested structures"""
364
+ # Find the first => that's not inside quotes or nested structures
365
+ paren_count = 0
366
+ bracket_count = 0
367
+ in_quotes = False
368
+ quote_char = ''
369
+
370
+ for i, char in enumerate(element):
371
+ if (char == '"' or char == "'") and not in_quotes:
372
+ in_quotes = True
373
+ quote_char = char
374
+ elif char == quote_char and in_quotes:
375
+ if i > 0 and element[i - 1] == '\\':
376
+ continue
377
+ in_quotes = False
378
+ quote_char = ''
379
+ elif not in_quotes:
380
+ if char == '(':
381
+ paren_count += 1
382
+ elif char == ')':
383
+ paren_count -= 1
384
+ elif char == '[':
385
+ bracket_count += 1
386
+ elif char == ']':
387
+ bracket_count -= 1
388
+ elif char == '=' and i + 1 < len(element) and element[i + 1] == '>' and paren_count == 0 and bracket_count == 0:
389
+ key = element[:i].strip()
390
+ value = element[i + 2:].strip()
391
+ return [key, value]
392
+
393
+ return [element]
394
+
395
+ def _convert_key(self, key: str) -> str:
396
+ """Convert array key to JavaScript key"""
397
+ key = key.strip()
398
+
399
+ # Remove quotes if present
400
+ if (key.startswith("'") and key.endswith("'")) or (key.startswith('"') and key.endswith('"')):
401
+ key = key[1:-1]
402
+
403
+ return key
404
+
405
+ def _convert_value(self, value: str) -> str:
406
+ """Convert array value to JavaScript value"""
407
+ value = value.strip()
408
+
409
+ # Handle different value types
410
+ if value.startswith("'") and value.endswith("'"):
411
+ # String value
412
+ return f'"{value[1:-1]}"'
413
+ elif value.startswith('"') and value.endswith('"'):
414
+ # String value
415
+ return value
416
+ elif value.isdigit() or value.replace('.', '').isdigit():
417
+ # Numeric value
418
+ return value
419
+ elif value.lower() in ['true', 'false', 'null']:
420
+ # Boolean/null values
421
+ return value.lower()
422
+ elif value.startswith('[') and value.endswith(']'):
423
+ # Nested array - recursive conversion
424
+ return self._convert_complex_structures(value)
425
+ elif value.startswith('"') or value.startswith("'"):
426
+ # Quoted string
427
+ return value
428
+ elif value.isalnum() or value.replace('_', '').isalnum():
429
+ # Simple identifier (variable name)
430
+ return value
431
+ else:
432
+ # Variable or expression
433
+ return self._convert_simple_expression(value)
434
+
435
+ def _handle_string_concatenation(self, expr: str) -> str:
436
+ """Handle string concatenation (. to +) while preserving object property access"""
437
+
438
+ # Handle string concatenation with variables and string literals
439
+ # But skip function calls like route('api.users') and object method calls like now().format()
440
+ # Also skip nested function calls like json_encode(event('view.rendered'))
441
+ if '.' in expr and ('$' in expr or '"' in expr or "'" in expr) and not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expr.strip()):
442
+ # Skip array literals like ['name' => 'John', 'email' => 'john@example.com']
443
+ if expr.strip().startswith('[') and expr.strip().endswith(']'):
444
+ return expr
445
+ # Skip object literals like {"name": "John", "email": "john@example.com"}
446
+ if expr.strip().startswith('{') and expr.strip().endswith('}'):
447
+ return expr
448
+ # Skip null coalescing operator like __VIEW_NAME__ ?? 'layouts.base'
449
+ if '??' in expr:
450
+ return expr
451
+ # Skip comparison operators like __VIEW_PATH__ === 'layouts.base'
452
+ # But check outside string literals to avoid matching < in '<strong>'
453
+ expr_without_strings = re.sub(r"'[^']*'", '', expr)
454
+ expr_without_strings = re.sub(r'"[^"]*"', '', expr_without_strings)
455
+ if re.search(r'[=!<>]=?|==|!=|<=|>=|===|!==', expr_without_strings):
456
+ return expr
457
+ # Skip ternary operators like condition ? value1 : value2
458
+ if re.search(r'\?.*:', expr):
459
+ return expr
460
+ # Also skip object method calls like now().format() or App.Helper.now().format()
461
+ # Pattern: method().method() or object.method().method()
462
+ if re.search(r'[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expr):
463
+ return expr
464
+ # Protect string literals FIRST before processing
465
+ string_literals = []
466
+ def protect_string_literal(match):
467
+ pattern = match.group(0)
468
+ placeholder = f"__STR_LIT_{len(string_literals)}__"
469
+ string_literals.append(pattern)
470
+ return placeholder
471
+
472
+ # Protect single-quoted strings
473
+ expr_protected = re.sub(r"'[^']*'", protect_string_literal, expr)
474
+ # Protect double-quoted strings
475
+ expr_protected = re.sub(r'"[^"]*"', protect_string_literal, expr_protected)
476
+
477
+ # Use regex to properly split string concatenation
478
+ # Pattern to match: variable, string literal placeholder, or other tokens (excluding . and spaces)
479
+ pattern = r'(\$[a-zA-Z_][a-zA-Z0-9_]*|__STR_LIT_\d+__|[^\s\.]+)'
480
+ parts = re.findall(pattern, expr_protected)
481
+
482
+ js_parts = []
483
+ for part in parts:
484
+ if not part or part == '.':
485
+ # Skip empty strings and dots
486
+ continue
487
+ if part.startswith('$'):
488
+ var_name = part[1:]
489
+ js_parts.append(var_name)
490
+ elif part.startswith('__STR_LIT_'):
491
+ # Restore string literal
492
+ idx = int(re.search(r'\d+', part).group())
493
+ literal = string_literals[idx]
494
+ if literal.startswith("'"):
495
+ js_parts.append(literal)
496
+ elif literal.startswith('"'):
497
+ inner = literal[1:-1]
498
+ inner = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', inner)
499
+ js_parts.append(f"`{inner}`")
500
+ else:
501
+ # Other tokens (shouldn't normally reach here)
502
+ js_parts.append(part)
503
+
504
+ return '+'.join(js_parts)
505
+
506
+ # Protect string literals FIRST before processing object access
507
+ string_literals = []
508
+ def protect_string_literal(match):
509
+ pattern = match.group(0)
510
+ placeholder = f"__STR_LIT_{len(string_literals)}__"
511
+ string_literals.append(pattern)
512
+ return placeholder
513
+
514
+ # Protect single-quoted strings
515
+ expr = re.sub(r"'[^']*'", protect_string_literal, expr)
516
+ # Protect double-quoted strings
517
+ expr = re.sub(r'"[^"]*"', protect_string_literal, expr)
518
+
519
+ # Protect object property access patterns
520
+ object_access_patterns = []
521
+ def protect_object_access(match):
522
+ pattern = match.group(0)
523
+ placeholder = f"__OBJ_ACCESS_{len(object_access_patterns)}__"
524
+ object_access_patterns.append(pattern)
525
+ return placeholder
526
+
527
+ # Protect object property access patterns (word.word)
528
+ expr = re.sub(r'\b\w+\.\w+\b', protect_object_access, expr)
529
+
530
+ # Convert remaining . to + for concatenation
531
+ expr = re.sub(r'\s+\.\s+', ' + ', expr)
532
+ expr = re.sub(r'\.\s+', ' + ', expr)
533
+ expr = re.sub(r'\s+\.', ' + ', expr)
534
+
535
+ # Restore object property access patterns
536
+ for i, pattern in enumerate(object_access_patterns):
537
+ expr = expr.replace(f"__OBJ_ACCESS_{i}__", pattern)
538
+
539
+ # Restore string literals LAST
540
+ for i, literal in enumerate(string_literals):
541
+ expr = expr.replace(f"__STR_LIT_{i}__", literal)
542
+
543
+ return expr
544
+
545
+ def _convert_simple_expression(self, expr: str) -> str:
546
+ """Convert simple PHP expression to JavaScript"""
547
+ expr = expr.strip()
548
+
549
+ # Convert object property access
550
+ expr = re.sub(r'->', '.', expr)
551
+
552
+ # Handle string concatenation
553
+ expr = self._handle_string_concatenation(expr)
554
+
555
+ # Add function prefixes
556
+ expr = self._add_function_prefixes(expr)
557
+
558
+ return expr
559
+
560
+ def _add_function_prefixes(self, expr: str) -> str:
561
+ """Add App.View. or App.Helper. prefix to PHP functions based on ViewConfig"""
562
+ from config import ViewConfig, APP_VIEW_NAMESPACE, APP_HELPER_NAMESPACE
563
+
564
+ # Get all known functions (View + Helper)
565
+ all_functions = [
566
+ 'count', 'min', 'max', 'abs', 'ceil', 'floor', 'round', 'sqrt',
567
+ 'strlen', 'substr', 'trim', 'ltrim', 'rtrim', 'strtolower', 'strtoupper',
568
+ 'isset', 'empty', 'is_null', 'is_array', 'is_string', 'is_numeric',
569
+ 'array_key_exists', 'in_array', 'array_merge', 'array_push', 'array_pop',
570
+ 'json_encode', 'json_decode', 'md5', 'sha1', 'base64_encode', 'base64_decode',
571
+ 'now', 'today', 'date', 'time', 'strtotime', 'mktime',
572
+ 'diffInDays', 'diffInHours', 'diffInMinutes', 'diffInSeconds',
573
+ 'addDays', 'subDays', 'addHours', 'subHours', 'addMinutes', 'subMinutes',
574
+ 'format', 'parse', 'createFromFormat',
575
+ 'env', 'config', 'auth', 'request', 'response', 'session', 'cache',
576
+ 'view', 'redirect', 'route', 'url', 'asset', 'mix',
577
+ 'collect', 'dd', 'dump', 'logger', 'abort', 'old', 'slug',
578
+ # Additional functions that might be used
579
+ 'ucfirst', 'lcfirst', 'str_replace', 'explode', 'implode', 'array_unique',
580
+ 'formatDate', 'formatNumber', 'formatCurrency', 'truncate', 'number_format',
581
+ 'updateTitle', 'updateDescription', 'updateKeywords',
582
+ 'getUrlParams', 'buildUrl', 'isInViewport', 'scrollTo', 'copyToClipboard',
583
+ 'getDeviceType', 'isMobile', 'isTablet', 'isDesktop'
584
+ ]
585
+
586
+ for func in all_functions:
587
+ pattern = r'(?<![\w.])(' + re.escape(func) + r')\s*\('
588
+ # Use ViewConfig to determine correct prefix
589
+ if ViewConfig.is_view_function(func):
590
+ replacement = f'{APP_VIEW_NAMESPACE}.\\1('
591
+ else:
592
+ replacement = f'{APP_HELPER_NAMESPACE}.\\1('
593
+ expr = re.sub(pattern, replacement, expr)
594
+
595
+ return expr
596
+
597
+
598
+ # Global instance for backward compatibility
599
+ _php_js_converter = PHPToJSConverter()
600
+
601
+ def php_to_js_advanced(expr: str) -> str:
602
+ """Advanced PHP to JavaScript conversion function"""
603
+ return _php_js_converter.convert_php_expression_to_js(expr)