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,1099 @@
1
+ """
2
+ Event Directive Processor - Xử lý event directives theo format mới
3
+ """
4
+
5
+ import re
6
+
7
+ class EventDirectiveProcessor:
8
+ def __init__(self, usestate_variables=None):
9
+ """
10
+ Initialize Event Directive Processor
11
+ @param usestate_variables: Set of variable names that have useState declarations
12
+ """
13
+ self.usestate_variables = usestate_variables or set()
14
+
15
+ def process_event_directive(self, event_type, expression):
16
+ """
17
+ Process event directive theo format mới
18
+ Input: @click(handleClick(Event, 'test'))
19
+ Output: ${self.addEventConfig("click", [{"handler": "handleClick", "params": ["@EVENT", "test"]}])}
20
+
21
+ Rules:
22
+ - Tất cả đều dùng __addEventConfig
23
+ - Hàm không có $ prefix: dùng object format {"handler": "name", "params": [...]}
24
+ - Biểu thức: dùng arrow function format (event) => ... hoặc () => ...
25
+ - Hàm có $ prefix:
26
+ - Nếu có useState → dùng (event) => setStateKey(...)
27
+ - Nếu không → dùng arrow function thông thường
28
+ """
29
+ try:
30
+ raw_expr = expression.strip()
31
+
32
+ # Split by comma để xử lý từng phần
33
+ parts = self.split_by_comma(raw_expr)
34
+
35
+ # Phân loại các parts: handlers (không có $) vs expressions (có $ hoặc biểu thức)
36
+ # Tất cả đều dùng __addEventConfig, chỉ khác format:
37
+ # - Handler functions → object format: {"handler": "name", "params": [...]}
38
+ # - Expressions → arrow function format: (event) => ... hoặc () => ...
39
+ # Giữ nguyên thứ tự như trong input
40
+ handler_items = []
41
+
42
+ for part in parts:
43
+ part = part.strip()
44
+ if not part:
45
+ continue
46
+
47
+ # Kiểm tra xem có phải là function call không có $ prefix không
48
+ if self._is_function_call_without_dollar(part):
49
+ # Hàm không có $ prefix → dùng object format
50
+ handler = self.parse_handler(part)
51
+ if handler:
52
+ # Parameters đã được xử lý bởi parse_handler_parameters
53
+ # (có thể là object config string hoặc giá trị thông thường)
54
+ processed_params = handler['params']
55
+
56
+ # Build handler object
57
+ params_str = ','.join(processed_params)
58
+ handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str}]}}'
59
+ handler_items.append(handler_str)
60
+ else:
61
+ # Biểu thức hoặc hàm có $ prefix
62
+ # Kiểm tra xem có nested function calls phức tạp không
63
+ # Nếu có → dùng object handler để chắc chắn hơn
64
+ if self._has_nested_function_calls(part):
65
+ # Có nested calls → parse như handler function
66
+ # Xử lý cả trường hợp có $ prefix (state setter)
67
+ handler = self._parse_handler_with_dollar(part)
68
+ if handler:
69
+ # Parse và process parameters (có thể chứa function calls)
70
+ params_string = ', '.join(handler['params'])
71
+ processed_params = self.parse_handler_parameters(params_string)
72
+
73
+ # Build handler object
74
+ params_str = ','.join(processed_params)
75
+ handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str}]}}'
76
+ handler_items.append(handler_str)
77
+ else:
78
+ # Nếu không parse được → dùng arrow function
79
+ expressions = self._split_expressions_by_semicolon(part)
80
+ for expr in expressions:
81
+ expr_js = self._process_expression_to_arrow(expr)
82
+ handler_items.append(expr_js)
83
+ else:
84
+ # Không có nested calls → dùng arrow function format
85
+ # Tách biểu thức có `;` thành nhiều arrow functions
86
+ # Ví dụ: $a++; $b-- → [() => a++, () => b--]
87
+ expressions = self._split_expressions_by_semicolon(part)
88
+ for expr in expressions:
89
+ expr_js = self._process_expression_to_arrow(expr)
90
+ handler_items.append(expr_js)
91
+
92
+ # Luôn dùng __addEventConfig với mixed format, giữ nguyên thứ tự
93
+ if handler_items:
94
+ handlers_str = f'[{",".join(handler_items)}]'
95
+ event_config = f'this.__addEventConfig("{event_type}", {handlers_str})'
96
+ return f"${{{event_config}}}"
97
+
98
+ return ''
99
+
100
+ except Exception as e:
101
+ print(f"Event directive error: {e}")
102
+ return ''
103
+
104
+ def parse_event_handlers(self, expression):
105
+ """
106
+ Parse multiple event handlers từ expression
107
+ """
108
+ handlers = []
109
+
110
+ # Split by comma, respecting nested parentheses
111
+ handler_strings = self.split_by_comma(expression)
112
+
113
+ for handler_string in handler_strings:
114
+ handler_string = handler_string.strip()
115
+ if not handler_string:
116
+ continue
117
+
118
+ # Parse handler name and parameters
119
+ handler = self.parse_handler(handler_string)
120
+ if handler:
121
+ handlers.append(handler)
122
+
123
+ return handlers
124
+
125
+ def parse_handler(self, handler_string):
126
+ """
127
+ Parse individual handler: handleClick(Event, 'test') or handleClick
128
+ Chỉ parse hàm không có $ prefix
129
+ """
130
+ # Match function name and parameters
131
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', handler_string, re.DOTALL)
132
+ if match:
133
+ handler_name = match.group(1)
134
+ params_string = match.group(2).strip()
135
+
136
+ # Parse parameters
137
+ params = self.parse_handler_parameters(params_string)
138
+
139
+ return {
140
+ 'handler': handler_name,
141
+ 'params': params
142
+ }
143
+ else:
144
+ # No parentheses - just function name
145
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)$', handler_string.strip())
146
+ if match:
147
+ handler_name = match.group(1)
148
+ return {
149
+ 'handler': handler_name,
150
+ 'params': []
151
+ }
152
+
153
+ return None
154
+
155
+ def parse_handler_parameters(self, params_string):
156
+ """
157
+ Parse handler parameters: Event, 'test', 123
158
+ Nếu param là function call → dùng object config
159
+ """
160
+ if not params_string.strip():
161
+ return []
162
+
163
+ # Split by comma, respecting nested structures
164
+ params = self.split_by_comma(params_string)
165
+
166
+ # Process each parameter
167
+ processed_params = []
168
+ for param in params:
169
+ param = param.strip()
170
+
171
+ # Kiểm tra xem có phải là function call không (có hoặc không có $ prefix)
172
+ # Nếu là function call → parse thành object config
173
+ if self._is_function_call_in_param(param):
174
+ handler = self._parse_function_call_in_param(param)
175
+ if handler:
176
+ # Build object config string
177
+ # in_params_context=True vì đang xử lý params trong object config
178
+ processed_handler_params = []
179
+ for p in handler['params']:
180
+ processed_handler_params.append(self.process_parameter(p.strip(), in_params_context=True))
181
+ params_str = ','.join(processed_handler_params)
182
+ handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str}]}}'
183
+ processed_params.append(handler_str)
184
+ continue
185
+
186
+ # Không phải function call → xử lý như bình thường
187
+ # in_params_context=True vì đang xử lý params trong object config
188
+ processed_param = self.process_parameter(param, in_params_context=True)
189
+ processed_params.append(processed_param)
190
+
191
+ return processed_params
192
+
193
+ def _is_function_call_in_param(self, param):
194
+ """
195
+ Kiểm tra xem param có phải là function call không (có hoặc không có $ prefix)
196
+ Ví dụ: outerFunc($count, @event) → True
197
+ Ví dụ: $setCount($count + 1) → True
198
+ Ví dụ: $count → False
199
+ """
200
+ param = param.strip()
201
+ # Match function call pattern: name(...) hoặc $name(...)
202
+ match = re.match(r'^(\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\(', param, re.DOTALL)
203
+ return match is not None
204
+
205
+ def _parse_function_call_in_param(self, param):
206
+ """
207
+ Parse function call trong param thành handler object
208
+ Ví dụ: outerFunc($count, @event) → {"handler": "outerFunc", "params": ["$count", "@event"]}
209
+ Ví dụ: $setCount($count + 1) → {"handler": "setCount", "params": ["$count + 1"]}
210
+ """
211
+ param = param.strip()
212
+
213
+ # Match function call với hoặc không có $ prefix
214
+ match = re.match(r'^(\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', param, re.DOTALL)
215
+ if match:
216
+ func_name_with_dollar = match.group(1)
217
+ params_string = match.group(2).strip()
218
+
219
+ # Xử lý tên hàm
220
+ if func_name_with_dollar.startswith('$'):
221
+ func_name = func_name_with_dollar[1:] # Bỏ $ prefix
222
+ # Kiểm tra xem có phải là state variable không
223
+ if func_name in self.usestate_variables:
224
+ # Là state setter → dùng tên setter
225
+ handler_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
226
+ else:
227
+ # Không phải state variable → dùng tên hàm bình thường
228
+ handler_name = func_name
229
+ else:
230
+ # Không có $ prefix
231
+ handler_name = func_name_with_dollar
232
+
233
+ # Parse parameters
234
+ params = self.split_by_comma(params_string)
235
+
236
+ return {
237
+ 'handler': handler_name,
238
+ 'params': params
239
+ }
240
+
241
+ return None
242
+
243
+ def _parse_handler_with_dollar(self, handler_string):
244
+ """
245
+ Parse handler có $ prefix (state setter)
246
+ Ví dụ: $setCount(nestedCall($count, $count + 1))
247
+ → {"handler": "setCount", "params": ["nestedCall($count, $count + 1)"]}
248
+ """
249
+ # Match function call với $ prefix: $name(...)
250
+ match = re.match(r'^\$([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', handler_string, re.DOTALL)
251
+ if match:
252
+ func_name = match.group(1)
253
+ params_string = match.group(2).strip()
254
+
255
+ # Kiểm tra xem có phải là state variable không
256
+ if func_name in self.usestate_variables:
257
+ # Là state setter → dùng tên setter
258
+ # If func_name already starts with "set", use it as is (it's already a setter name)
259
+ # Otherwise, capitalize first letter: count -> setCount
260
+ if func_name.startswith('set') and len(func_name) > 3:
261
+ setter_name = func_name # Already a setter name like "setCount"
262
+ else:
263
+ setter_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
264
+
265
+ # Parse parameters
266
+ params = self.split_by_comma(params_string)
267
+
268
+ return {
269
+ 'handler': setter_name,
270
+ 'params': params
271
+ }
272
+ else:
273
+ # Không phải state variable → dùng tên hàm bình thường (bỏ $)
274
+ params = self.split_by_comma(params_string)
275
+
276
+ return {
277
+ 'handler': func_name,
278
+ 'params': params
279
+ }
280
+
281
+ return None
282
+
283
+ def _has_event_with_access(self, param):
284
+ """
285
+ Kiểm tra xem param có chứa $event với property access hoặc method call không
286
+ Ví dụ: $event.target, $event.preventDefault(), test($event.type)
287
+ """
288
+ # Pattern: $event theo sau bởi . hoặc chữ khác (property/method access)
289
+ # Hoặc $event nằm trong function call: func($event.prop)
290
+ pattern = r'\$(?:event|Event|EVENT)\s*\.'
291
+ return re.search(pattern, param, re.IGNORECASE) is not None
292
+
293
+ def _convert_event_with_access_to_arrow(self, param):
294
+ """
295
+ Convert expression có $event.property hoặc $event.method() thành arrow function
296
+ Input: $event.target → Output: (event) => event.target
297
+ Input: test($event.type, $message) → Output: (event) => test(event.type, message)
298
+ """
299
+ # Convert $event/$Event/$EVENT thành event (JavaScript variable)
300
+ result = re.sub(r'\$(?:event|Event|EVENT)(?![a-zA-Z])', 'event', param, flags=re.IGNORECASE)
301
+
302
+ # Convert các PHP variables khác thành JS variables
303
+ result = self.convert_php_variable_to_js(result)
304
+
305
+ # Convert PHP array syntax to JS object
306
+ result = self.convert_php_array_to_js_object(result)
307
+
308
+ # Wrap trong arrow function
309
+ return f'(event) => {result}'
310
+
311
+ def process_parameter(self, param, in_params_context=False):
312
+ """
313
+ Process individual parameter để handle special cases
314
+ Nếu là biểu thức → trả về (event) => ... format
315
+ Nếu không → trả về giá trị thông thường
316
+ in_params_context: True nếu đang xử lý params trong object config
317
+ """
318
+ # Nếu đã là object config string (có dạng {"handler":...}), không xử lý lại
319
+ if isinstance(param, str) and param.strip().startswith('{"handler"'):
320
+ return param
321
+
322
+ # SPECIAL CASE: Nếu có $event với property/method access → convert thành arrow function
323
+ # Ví dụ: $event.target → (event) => event.target
324
+ # Phải làm TRƯỚC các bước normalize khác
325
+ if self._has_event_with_access(param):
326
+ return self._convert_event_with_access_to_arrow(param)
327
+
328
+ # QUAN TRỌNG: Normalize event aliases TRƯỚC khi convert PHP variables
329
+ # Thêm alias support: $event/$Event/$EVENT → @EVENT (phải làm trước convert_php_variable_to_js)
330
+ param = re.sub(r'\$(?:event|Event|EVENT)(?![a-zA-Z])', '@EVENT', param, flags=re.IGNORECASE)
331
+
332
+ # Đảm bảo @event/@Event/@EVENT được chuyển thành @EVENT (chuẩn hóa)
333
+ param = re.sub(r'@(?:event|Event|EVENT)(?![a-zA-Z])', '@EVENT', param, flags=re.IGNORECASE)
334
+
335
+ # @EVENT luôn là chuỗi "@EVENT" để hệ thống thay thế sau
336
+ if param.strip() == '@EVENT':
337
+ return '"@EVENT"'
338
+
339
+ # Convert PHP variable to JavaScript variable ($item -> item)
340
+ # Phải làm SAU khi đã normalize event aliases
341
+ param = self.convert_php_variable_to_js(param)
342
+
343
+ # Xử lý Event parameters trong mọi context
344
+ param = self.process_event_in_string(param)
345
+
346
+ # Xử lý @attr(...) -> "#ATTR:..."
347
+ param = self.process_attr_prop_in_string(param, '@attr', '#ATTR')
348
+
349
+ # Xử lý @prop(...) -> "#PROP:..."
350
+ param = self.process_attr_prop_in_string(param, '@prop', '#PROP')
351
+
352
+ # Xử lý @val(...) -> "#VALUE:..."
353
+ param = self.process_attr_prop_in_string(param, '@val', '#VALUE')
354
+
355
+ # Xử lý @value(...) -> "#VALUE:..."
356
+ param = self.process_attr_prop_in_string(param, '@value', '#VALUE')
357
+
358
+ # Convert PHP array syntax to JavaScript object syntax
359
+ param = self.convert_php_array_to_js_object(param)
360
+
361
+ # Nếu đã là arrow function (từ các bước xử lý trước), không wrap lại
362
+ if '=>' in param:
363
+ # Nhưng vẫn cần thay thế @EVENT trong arrow function nếu có
364
+ param = re.sub(r'(?<!")@(?:EVENT|event|Event)(?!")', '"@EVENT"', param)
365
+ return param
366
+
367
+ # Thay thế @EVENT/@event/@Event (không có quotes) thành "@EVENT" trong expression
368
+ # Sử dụng regex để tránh thay thế @EVENT đã có quotes
369
+ param = re.sub(r'(?<!")@(?:EVENT|event|Event)(?!")', '"@EVENT"', param, flags=re.IGNORECASE)
370
+
371
+ # Nếu đang xử lý params trong object config và param là biến đơn giản
372
+ # (không phải string, không phải directive đã xử lý) → wrap trong () => để lấy giá trị runtime
373
+ if in_params_context:
374
+ # Kiểm tra xem có phải là string (đã có quotes) không
375
+ is_string = (param.startswith('"') and param.endswith('"')) or (param.startswith("'") and param.endswith("'"))
376
+ # Kiểm tra xem có phải là directive đã xử lý không (#ATTR, #PROP, #VALUE)
377
+ is_directive = param.startswith('"#') or param.startswith("'#")
378
+ # Kiểm tra xem có phải là biến đơn giản (valid JS identifier) không
379
+ is_simple_var = self._is_valid_js_identifier(param) and not is_string and not is_directive
380
+
381
+ if is_simple_var:
382
+ return f'() => {param}'
383
+
384
+ # Nếu là biểu thức → wrap trong arrow function
385
+ if self._looks_like_expression(param):
386
+ return f'(event) => {param}'
387
+
388
+ return param
389
+
390
+ def process_data_type(self, param):
391
+ """
392
+ Process parameter để xác định đúng kiểu dữ liệu
393
+ """
394
+ param = param.strip()
395
+
396
+ # Nếu đã có quotes thì giữ nguyên
397
+ if (param.startswith('"') and param.endswith('"')) or (param.startswith("'") and param.endswith("'")):
398
+ return param
399
+
400
+ # Nếu là object hoặc array JavaScript thì giữ nguyên
401
+ if (param.startswith('{') and param.endswith('}')) or (param.startswith('[') and param.endswith(']')):
402
+ return param
403
+
404
+ # Nếu là number thì giữ nguyên
405
+ if param.isdigit() or (param.startswith('-') and param[1:].isdigit()):
406
+ return param
407
+
408
+ # Nếu là boolean thì giữ nguyên
409
+ if param.lower() in ['true', 'false']:
410
+ return param
411
+
412
+ # Nếu là null thì giữ nguyên
413
+ if param.lower() == 'null':
414
+ return param
415
+
416
+ # Nếu là string thì wrap trong quotes
417
+ return f'"{param}"'
418
+
419
+ def convert_php_array_to_js_object(self, param):
420
+ """
421
+ Convert PHP array syntax to JavaScript object syntax
422
+ ['key' => 'value'] -> {"key": "value"}
423
+ """
424
+ # Use the new PHP to JS converter with proper precedence
425
+ from php_converter import convert_php_to_js
426
+ param = convert_php_to_js(param)
427
+
428
+ # If it's already a JavaScript object/array, return as is
429
+ if param.startswith('{') and param.endswith('}'):
430
+ return param
431
+ if param.startswith('[') and param.endswith(']') and ' => ' not in param:
432
+ return param
433
+
434
+ # If it's a PHP array, convert to JavaScript object
435
+ if param.startswith('[') and param.endswith(']'):
436
+ # Use advanced converter for complex structures
437
+ from php_js_converter import php_to_js_advanced
438
+ param = php_to_js_advanced(param)
439
+
440
+ return param
441
+
442
+ def _fix_nested_arrays(self, param):
443
+ """
444
+ Fix nested arrays in converted object
445
+ {"test": {"hahaha", 12}} -> {"test": ["hahaha", 12]}
446
+ """
447
+ # Find patterns like {"value1", "value2"} and convert to ["value1", "value2"]
448
+ import re
449
+
450
+ # Pattern to match {"value1", "value2"} - array values
451
+ pattern = r'\{\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\}'
452
+
453
+ def replace_array(match):
454
+ val1 = match.group(1)
455
+ val2 = match.group(2)
456
+ return f'["{val1}", "{val2}"]'
457
+
458
+ param = re.sub(pattern, replace_array, param)
459
+
460
+ # Pattern to match {"value1", 123} - mixed array values
461
+ pattern2 = r'\{\s*"([^"]+)"\s*,\s*(\d+)\s*\}'
462
+
463
+ def replace_mixed_array(match):
464
+ val1 = match.group(1)
465
+ val2 = match.group(2)
466
+ return f'["{val1}", {val2}]'
467
+
468
+ param = re.sub(pattern2, replace_mixed_array, param)
469
+
470
+ return param
471
+
472
+ def process_event_in_string(self, param):
473
+ """
474
+ Process Event parameters trong string một cách recursive
475
+ Không xử lý 'event' trong arrow function như (event) => ...
476
+ """
477
+ # Nếu đã là arrow function, không xử lý (tránh match 'event' trong (event) =>)
478
+ if '=>' in param:
479
+ return param
480
+
481
+ # Nếu có pattern (event) hoặc (Event), không xử lý (đó là arrow function parameter)
482
+ if re.search(r'\(\s*event\s*\)', param, re.IGNORECASE):
483
+ return param
484
+
485
+ # Tìm tất cả Event variations trong string
486
+ # Match @event, @Event, @EVENT (với @ prefix)
487
+ # Không match 'event' đơn lẻ (tránh match trong các từ khác)
488
+ # Cho phép @event trước dấu ), dấu phẩy, khoảng trắng, hoặc kết thúc string
489
+ # Pattern: @event, @Event, @EVENT (không có chữ cái sau)
490
+ event_pattern = r'@(?:Event|EVENT|event)(?![a-zA-Z])'
491
+ param = re.sub(event_pattern, '@EVENT', param, flags=re.IGNORECASE)
492
+
493
+ # Thêm alias support: $event, $Event, $EVENT cũng chuyển thành @EVENT
494
+ # Pattern: $event, $Event, $EVENT (không có chữ cái sau)
495
+ dollar_event_pattern = r'\$(?:Event|EVENT|event)(?![a-zA-Z])'
496
+ param = re.sub(dollar_event_pattern, '@EVENT', param, flags=re.IGNORECASE)
497
+
498
+ return param
499
+
500
+ def process_attr_prop_in_string(self, param, directive, prefix):
501
+ """
502
+ Process @attr, @prop, @val, @value trong string một cách recursive
503
+ """
504
+ # Tìm tất cả @directive(...) hoặc @directive() trong string
505
+ pattern = re.escape(directive) + r'\s*\(\s*([^)]*)\s*\)'
506
+
507
+ def replace_match(match):
508
+ value = match.group(1).strip()
509
+
510
+ if not value:
511
+ # Trường hợp @directive() - không có tham số
512
+ return f'"{prefix}"'
513
+ else:
514
+ # Loại bỏ quotes nếu có
515
+ value = value.strip('"\'')
516
+ # Thay thế @directive(...) bằng "#PREFIX:..." hoặc "#PREFIX"
517
+ return f'"{prefix}:{value}"'
518
+
519
+ param = re.sub(pattern, replace_match, param, flags=re.IGNORECASE)
520
+
521
+ return param
522
+
523
+ def _split_expressions_by_semicolon(self, expr):
524
+ """
525
+ Tách biểu thức có `;` thành nhiều biểu thức
526
+ Ví dụ: $a++; $b-- → ['$a++', '$b--']
527
+ """
528
+ if ';' not in expr:
529
+ return [expr]
530
+
531
+ expressions = []
532
+ current = ''
533
+ paren_count = 0
534
+ bracket_count = 0
535
+ in_quotes = False
536
+ quote_char = ''
537
+
538
+ i = 0
539
+ while i < len(expr):
540
+ char = expr[i]
541
+
542
+ if not in_quotes:
543
+ if char in ['"', "'"]:
544
+ in_quotes = True
545
+ quote_char = char
546
+ current += char
547
+ elif char == '(':
548
+ paren_count += 1
549
+ current += char
550
+ elif char == ')':
551
+ paren_count -= 1
552
+ current += char
553
+ elif char == '[':
554
+ bracket_count += 1
555
+ current += char
556
+ elif char == ']':
557
+ bracket_count -= 1
558
+ current += char
559
+ elif char == ';' and paren_count == 0 and bracket_count == 0:
560
+ # Semicolon at level 0 - expression separator
561
+ if current.strip():
562
+ expressions.append(current.strip())
563
+ current = ''
564
+ i += 1
565
+ continue
566
+ else:
567
+ current += char
568
+ else:
569
+ current += char
570
+ if char == quote_char and (i == 0 or expr[i-1] != '\\'):
571
+ in_quotes = False
572
+ quote_char = ''
573
+
574
+ i += 1
575
+
576
+ # Add last expression
577
+ if current.strip():
578
+ expressions.append(current.strip())
579
+
580
+ return expressions if expressions else [expr]
581
+
582
+ def split_by_comma(self, expression):
583
+ """
584
+ Split expression by comma, respecting nested parentheses, square brackets and quotes
585
+ """
586
+ params = []
587
+ current = ''
588
+ paren_count = 0
589
+ bracket_count = 0
590
+ in_quotes = False
591
+ quote_char = ''
592
+
593
+ i = 0
594
+ while i < len(expression):
595
+ char = expression[i]
596
+
597
+ if not in_quotes:
598
+ if char in ['"', "'"]:
599
+ in_quotes = True
600
+ quote_char = char
601
+ current += char
602
+ elif char == '(':
603
+ paren_count += 1
604
+ current += char
605
+ elif char == ')':
606
+ paren_count -= 1
607
+ current += char
608
+ elif char == '[':
609
+ bracket_count += 1
610
+ current += char
611
+ elif char == ']':
612
+ bracket_count -= 1
613
+ current += char
614
+ elif char == ',' and paren_count == 0 and bracket_count == 0:
615
+ # Comma at level 0 - parameter separator
616
+ params.append(current.strip())
617
+ current = ''
618
+ i += 1
619
+ continue
620
+ else:
621
+ current += char
622
+ else:
623
+ current += char
624
+ if char == quote_char and (i == 0 or expression[i-1] != '\\'):
625
+ in_quotes = False
626
+ quote_char = ''
627
+
628
+ i += 1
629
+
630
+ # Add last parameter
631
+ if current.strip():
632
+ params.append(current.strip())
633
+
634
+ return params
635
+
636
+ def build_event_config(self, event_type, handlers):
637
+ """
638
+ Build event config theo format mới
639
+ """
640
+ handler_configs = []
641
+
642
+ for handler in handlers:
643
+ handler_name = handler['handler']
644
+ params = handler['params']
645
+
646
+ # Process parameters để handle special cases
647
+ processed_params = []
648
+ for param in params:
649
+ processed_params.append(self.process_parameter(param))
650
+
651
+ handler_configs.append({
652
+ 'handler': handler_name,
653
+ 'params': processed_params
654
+ })
655
+
656
+ # Build JavaScript array string manually để control quoting
657
+ handlers_str = self.build_handlers_string(handler_configs)
658
+
659
+ return f'this.__addEventConfig("{event_type}", {handlers_str})'
660
+
661
+ def build_handlers_string(self, handler_configs):
662
+ """
663
+ Build JavaScript array string manually để control quoting
664
+ """
665
+ handlers = []
666
+
667
+ for config in handler_configs:
668
+ handler_name = config['handler']
669
+ params = config['params']
670
+
671
+ # Build params array with proper quoting
672
+ processed_params = []
673
+ for param in params:
674
+ # Param đã được xử lý bởi process_parameter, có thể đã là arrow function
675
+ # Kiểm tra xem đã là arrow function chưa
676
+ if isinstance(param, str) and ('=>' in param or param.startswith('(event) =>') or param.startswith('() =>')):
677
+ # Đã là arrow function → giữ nguyên
678
+ processed_params.append(param)
679
+ continue
680
+
681
+ # @EVENT đã được xử lý trong process_parameter thành "@EVENT" (có quotes)
682
+ if isinstance(param, str) and (param == '@EVENT' or param == '"@EVENT"'):
683
+ processed_params.append('"@EVENT"')
684
+ continue
685
+
686
+ if isinstance(param, str):
687
+ # First, convert PHP variable to JS if needed ($item -> item)
688
+ param = self.convert_php_variable_to_js(param)
689
+
690
+ # If it's a string and not already quoted, check what it is
691
+ if not (param.startswith('"') and param.endswith('"')) and not (param.startswith("'") and param.endswith("'")):
692
+ # Check if it's a complex structure (array/object)
693
+ if param.startswith('[') and param.endswith(']'):
694
+ processed_params.append(param)
695
+ elif param.startswith('{') and param.endswith('}'):
696
+ processed_params.append(param)
697
+ elif param.startswith('@'):
698
+ # Special directive like @EVENT
699
+ processed_params.append(f'"{param}"')
700
+ elif self._is_valid_js_identifier(param):
701
+ # Valid JavaScript variable/identifier - don't wrap in quotes
702
+ processed_params.append(param)
703
+ else:
704
+ # Simple string value
705
+ processed_params.append(f'"{param}"')
706
+ else:
707
+ processed_params.append(param)
708
+ else:
709
+ # Convert to string for non-string types
710
+ processed_params.append(str(param))
711
+
712
+ params_str = ','.join(processed_params)
713
+
714
+ # Build handler object
715
+ handler_str = f'{{"handler":"{handler_name}","params":[{params_str}]}}'
716
+ handlers.append(handler_str)
717
+
718
+ return f'[{",".join(handlers)}]'
719
+
720
+ def _looks_like_expression(self, s: str) -> bool:
721
+ """
722
+ Phát hiện chuỗi là biểu thức JS (không phải literal/identifier đơn)
723
+ - Chứa toán tử, dấu ngoặc, hoặc gọi hàm
724
+ - Không phải chỉ là số, boolean, null, hay identifier hợp lệ
725
+ - Không phải @EVENT (đã là special directive)
726
+ """
727
+ t = s.strip()
728
+
729
+ # Nếu đã là arrow function, không phải biểu thức đơn giản
730
+ if '=>' in t:
731
+ return False
732
+
733
+ # @EVENT là special directive, không phải biểu thức
734
+ if t == '@EVENT' or t == '"@EVENT"':
735
+ return False
736
+
737
+ # literals
738
+ if t.isdigit() or (t.startswith('-') and t[1:].isdigit()):
739
+ return False
740
+ if t.lower() in ['true', 'false', 'null']:
741
+ return False
742
+ if self._is_valid_js_identifier(t):
743
+ return False
744
+ # function call or operators/parentheses
745
+ op_chars = ['+', '-', '*', '/', '%', '>', '<', '=', '!', '&', '|', '?', ':']
746
+ if '(' in t and ')' in t:
747
+ return True
748
+ if any(ch in t for ch in op_chars):
749
+ return True
750
+ # spaces between tokens can indicate expression
751
+ if ' ' in t:
752
+ return True
753
+ return False
754
+
755
+ def _looks_like_handlers(self, expr: str) -> bool:
756
+ """
757
+ Kiểm tra biểu thức giống danh sách handler function calls, ví dụ: fn(a), fn2(), handle
758
+ """
759
+ parts = self.split_by_comma(expr)
760
+ if not parts:
761
+ return False
762
+ for p in parts:
763
+ p = p.strip()
764
+ # Check if it's a function call with parameters
765
+ match = re.match(r'^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\((.*)\)$', p)
766
+ if match:
767
+ params_str = match.group(1).strip()
768
+ # If parameters contain expressions, this should be quickHandle
769
+ if params_str and self._contains_expressions(params_str):
770
+ return False # Not a simple handler, use quickHandle
771
+ elif not re.match(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$', p):
772
+ # Not a simple variable or function call
773
+ return False
774
+ return True
775
+
776
+ def _contains_expressions(self, params_str: str) -> bool:
777
+ """
778
+ Check if parameter string contains expressions (operators, method calls, etc.)
779
+ """
780
+ params_str = params_str.strip()
781
+ if not params_str:
782
+ return False
783
+
784
+ # Check for mathematical operators
785
+ if re.search(r'[+\-*/%]', params_str):
786
+ return True
787
+
788
+ # Check for method calls like obj.method() or obj->method()
789
+ if '->' in params_str or re.search(r'\.[a-zA-Z_]', params_str):
790
+ return True
791
+
792
+ # Check for array access like arr[0]
793
+ if re.search(r'\[.*\]', params_str):
794
+ return True
795
+
796
+ # Check for ternary operators
797
+ if '?' in params_str and ':' in params_str:
798
+ return True
799
+
800
+ return False
801
+
802
+ def _is_valid_js_identifier(self, param):
803
+ """
804
+ Check if param is a valid JavaScript identifier/variable name
805
+ Valid identifiers: letters, digits, underscore, dollar sign
806
+ Must start with letter, underscore, or dollar sign
807
+ Examples: item, userState, _private, $local
808
+ """
809
+ # JavaScript identifier pattern
810
+ js_identifier_pattern = r'^[a-zA-Z_$][a-zA-Z0-9_$]*$'
811
+ return bool(re.match(js_identifier_pattern, param))
812
+
813
+ def convert_php_variable_to_js(self, param):
814
+ """
815
+ Convert PHP variable syntax to JavaScript
816
+ $item -> item
817
+ """
818
+ # Pattern to match PHP variables: $variableName
819
+ php_var_pattern = r'\$([a-zA-Z_][a-zA-Z0-9_]*)'
820
+ # Replace $variable with variable
821
+ param = re.sub(php_var_pattern, r'\1', param)
822
+ return param
823
+
824
+ def _has_nested_function_calls(self, expr):
825
+ """
826
+ Kiểm tra xem expression có chứa nested function calls không
827
+ Ví dụ: nestedCall(outerFunc(...), innerFunc(...)) → True
828
+ Ví dụ: $setCount(nestedCall(...)) → True
829
+ Ví dụ: test($a, $b) → False
830
+ """
831
+ expr = expr.strip()
832
+
833
+ # Tìm function call pattern: name(...) hoặc $name(...)
834
+ match = re.match(r'^(\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', expr, re.DOTALL)
835
+ if match:
836
+ params_string = match.group(2).strip()
837
+ if not params_string:
838
+ return False
839
+
840
+ # Đếm số function calls trong params (không tính function call ngoài cùng)
841
+ # Pattern: tên hàm + ( (có thể có $ prefix)
842
+ nested_pattern = r'\$?[a-zA-Z_][a-zA-Z0-9_]*\s*\('
843
+ matches = re.findall(nested_pattern, params_string)
844
+ # Nếu có nhiều hơn 0 function calls trong params → có nested calls
845
+ return len(matches) > 0
846
+
847
+ return False
848
+
849
+ def _is_function_call_without_dollar(self, expr):
850
+ """
851
+ Kiểm tra xem có phải là function call không có $ prefix và KHÔNG phải state variable
852
+ Tất cả function calls không có $ prefix → luôn dùng object handler
853
+ Ví dụ: test(), handleClick(Event), nestedCall(...) → True (handler function)
854
+ Ví dụ: $test(), $press++, setCount(...) → False (có $ hoặc biểu thức)
855
+ Ví dụ: count(...) nếu count có trong usestate_variables → False (state variable, không phải handler)
856
+ """
857
+ expr = expr.strip()
858
+
859
+ # Match function call pattern: name(...)
860
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', expr, re.DOTALL)
861
+ if match:
862
+ func_name = match.group(1)
863
+ # Không có $ prefix
864
+ if not func_name.startswith('$'):
865
+ # Kiểm tra xem có phải là state variable không (có trong usestate_variables)
866
+ # Nếu là state variable → không phải handler function → xử lý như biểu thức
867
+ if func_name in self.usestate_variables:
868
+ return False # State variable → xử lý như biểu thức
869
+
870
+ # Tất cả function calls không có $ prefix → luôn là handler function (dùng object handler)
871
+ return True
872
+
873
+ # Match simple function name: name
874
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)$', expr)
875
+ if match:
876
+ func_name = match.group(1)
877
+ if not func_name.startswith('$'):
878
+ # Kiểm tra xem có phải là state variable không
879
+ if func_name in self.usestate_variables:
880
+ return False # State variable → xử lý như biểu thức
881
+ return True
882
+
883
+ return False
884
+
885
+ def _process_expression_to_arrow(self, expr):
886
+ """
887
+ Xử lý biểu thức thành arrow function format
888
+ - Nếu có $ prefix và có useState → dùng (event) => setStateKey(...)
889
+ - Nếu không → dùng (event) => ... hoặc () => ...
890
+ """
891
+ original_expr = expr.strip()
892
+
893
+ # Convert PHP variable to JavaScript
894
+ expr_js = self.convert_php_variable_to_js(original_expr)
895
+
896
+ # Xử lý Event parameters
897
+ expr_js = self.process_event_in_string(expr_js)
898
+
899
+ # Xử lý @attr, @prop, @val, @value
900
+ expr_js = self.process_attr_prop_in_string(expr_js, '@attr', '#ATTR')
901
+ expr_js = self.process_attr_prop_in_string(expr_js, '@prop', '#PROP')
902
+ expr_js = self.process_attr_prop_in_string(expr_js, '@val', '#VALUE')
903
+ expr_js = self.process_attr_prop_in_string(expr_js, '@value', '#VALUE')
904
+
905
+ # Convert PHP array syntax to JavaScript object syntax
906
+ expr_js = self.convert_php_array_to_js_object(expr_js)
907
+
908
+ # Kiểm tra xem có phải là function call không có $ prefix không
909
+ # Ví dụ: setCount($count - 1) → setCount(count - 1)
910
+ # Ví dụ: count(...) nếu count có trong usestate_variables → cần dùng setter
911
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', original_expr, re.DOTALL)
912
+ if match:
913
+ func_name = match.group(1)
914
+ params_str = match.group(2).strip()
915
+
916
+ # Nếu là state variable (có trong usestate_variables), cần dùng setter
917
+ if func_name in self.usestate_variables:
918
+ # Parse params - nếu param là function call (không phải state variable) → dùng object config
919
+ params = self.split_by_comma(params_str)
920
+ processed_params = []
921
+ for param in params:
922
+ param = param.strip()
923
+ # Kiểm tra xem có phải là function call không
924
+ if self._is_function_call_in_param(param):
925
+ # Function call → parse thành object config
926
+ handler = self._parse_function_call_in_param(param)
927
+ if handler:
928
+ # Build object config string
929
+ processed_handler_params = []
930
+ for p in handler['params']:
931
+ processed_handler_params.append(self.process_parameter(p.strip(), in_params_context=True))
932
+ params_str_inner = ','.join(processed_handler_params)
933
+ handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str_inner}]}}'
934
+ processed_params.append(handler_str)
935
+ continue
936
+
937
+ # Không phải function call → xử lý như bình thường
938
+ param_js = self.convert_php_variable_to_js(param)
939
+ param_js = self.process_event_in_string(param_js)
940
+ param_js = self.process_attr_prop_in_string(param_js, '@attr', '#ATTR')
941
+ param_js = self.process_attr_prop_in_string(param_js, '@prop', '#PROP')
942
+ param_js = self.process_attr_prop_in_string(param_js, '@val', '#VALUE')
943
+ param_js = self.process_attr_prop_in_string(param_js, '@value', '#VALUE')
944
+ param_js = self.convert_php_array_to_js_object(param_js)
945
+ # Thay thế @EVENT thành "@EVENT" nếu chưa có quotes
946
+ param_js = re.sub(r'(?<!")@EVENT(?!")', '"@EVENT"', param_js)
947
+ processed_params.append(param_js)
948
+
949
+ params_str_js = ', '.join(processed_params)
950
+ # If func_name already starts with "set", use it as is (it's already a setter name)
951
+ # Otherwise, capitalize first letter: count -> setCount
952
+ if func_name.startswith('set') and len(func_name) > 3:
953
+ setter_name = func_name # Already a setter name like "setCount"
954
+ else:
955
+ setter_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
956
+ return f'(event) => {setter_name}({params_str_js})'
957
+
958
+ # Kiểm tra xem có phải là function call với $ prefix không
959
+ # Ví dụ: $test($a+1) → test(a + 1)
960
+ # Ví dụ: $setCount($count + 1) → setCount(count + 1)
961
+ match = re.match(r'^\$([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', original_expr, re.DOTALL)
962
+ if match:
963
+ func_name = match.group(1)
964
+ params_str = match.group(2).strip()
965
+
966
+ # Kiểm tra xem có phải là state variable hoặc setter không (có trong usestate_variables)
967
+ # Nếu là state variable hoặc setter → cần dùng setter
968
+ if func_name in self.usestate_variables:
969
+ # Có useState → dùng (event) => setStateKey(...)
970
+ # Parse params - nếu param là function call (không phải state variable) → dùng object config
971
+ params = self.split_by_comma(params_str)
972
+ processed_params = []
973
+ for param in params:
974
+ param = param.strip()
975
+ # Kiểm tra xem có phải là function call không
976
+ if self._is_function_call_in_param(param):
977
+ # Function call → parse thành object config
978
+ handler = self._parse_function_call_in_param(param)
979
+ if handler:
980
+ # Build object config string
981
+ processed_handler_params = []
982
+ for p in handler['params']:
983
+ processed_handler_params.append(self.process_parameter(p.strip(), in_params_context=True))
984
+ params_str_inner = ','.join(processed_handler_params)
985
+ handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str_inner}]}}'
986
+ processed_params.append(handler_str)
987
+ continue
988
+
989
+ # Không phải function call → xử lý như bình thường
990
+ param_js = self.convert_php_variable_to_js(param)
991
+ # Xử lý Event parameters
992
+ param_js = self.process_event_in_string(param_js)
993
+ # Chuẩn hóa @event thành @EVENT
994
+ param_js = re.sub(r'@(?:event|Event|EVENT)(?![a-zA-Z])', '@EVENT', param_js, flags=re.IGNORECASE)
995
+ # Xử lý @attr, @prop, @val, @value
996
+ param_js = self.process_attr_prop_in_string(param_js, '@attr', '#ATTR')
997
+ param_js = self.process_attr_prop_in_string(param_js, '@prop', '#PROP')
998
+ param_js = self.process_attr_prop_in_string(param_js, '@val', '#VALUE')
999
+ param_js = self.process_attr_prop_in_string(param_js, '@value', '#VALUE')
1000
+ # Convert PHP array syntax to JavaScript object syntax
1001
+ param_js = self.convert_php_array_to_js_object(param_js)
1002
+ # Thay thế @EVENT thành "@EVENT" nếu chưa có quotes
1003
+ param_js = re.sub(r'(?<!")@EVENT(?!")', '"@EVENT"', param_js)
1004
+ processed_params.append(param_js)
1005
+
1006
+ params_str_js = ', '.join(processed_params)
1007
+ # If func_name already starts with "set", use it as is (it's already a setter name)
1008
+ # Otherwise, capitalize first letter: count -> setCount
1009
+ if func_name.startswith('set') and len(func_name) > 3:
1010
+ setter_name = func_name # Already a setter name like "setCount"
1011
+ else:
1012
+ setter_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
1013
+ return f'(event) => {setter_name}({params_str_js})'
1014
+ else:
1015
+ # Không có useState → dùng arrow function thông thường
1016
+ return f'(event) => {expr_js}'
1017
+
1018
+ # Kiểm tra xem có phải là biểu thức với $ variable không
1019
+ # Ví dụ: $press++, $hoverCount+=2
1020
+ if re.search(r'\$[a-zA-Z_][a-zA-Z0-9_]*', original_expr):
1021
+ # Có $ variable → kiểm tra useState
1022
+ # Extract variable name
1023
+ var_match = re.search(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', original_expr)
1024
+ if var_match:
1025
+ var_name = var_match.group(1)
1026
+ if var_name in self.usestate_variables:
1027
+ # Có useState → dùng (event) => setStateKey(...)
1028
+ # Convert expression: $press++ → press + 1
1029
+ # Hoặc giữ nguyên expression và wrap trong setter
1030
+ # Setter name format: setStateKey (capitalize first letter)
1031
+ setter_name = f'set{var_name[0].upper()}{var_name[1:]}' if var_name and len(var_name) > 0 else f'set{var_name}'
1032
+ # For assignment: $press++ → setPress(press + 1)
1033
+ # For simple increment: $press++ → setPress(press + 1)
1034
+ if '++' in expr_js:
1035
+ expr_js = expr_js.replace('++', '')
1036
+ return f'(event) => {setter_name}({expr_js} + 1)'
1037
+ elif '+=' in expr_js:
1038
+ # $hoverCount+=2 → setHoverCount(hoverCount + 2)
1039
+ parts = expr_js.split('+=')
1040
+ if len(parts) == 2:
1041
+ var = parts[0].strip()
1042
+ value = parts[1].strip()
1043
+ return f'(event) => {setter_name}({var} + {value})'
1044
+ elif '=' in expr_js and not expr_js.startswith('='):
1045
+ # $test = $hoverCount + 2 → setTest(hoverCount + 2)
1046
+ parts = expr_js.split('=', 1)
1047
+ if len(parts) == 2:
1048
+ var = parts[0].strip()
1049
+ value = parts[1].strip()
1050
+ # Setter name format: setStateKey (capitalize first letter)
1051
+ setter_name = f'set{var[0].upper()}{var[1:]}' if var and len(var) > 0 else f'set{var}'
1052
+ return f'(event) => {setter_name}({value})'
1053
+ else:
1054
+ # Other expression with useState variable
1055
+ return f'(event) => {setter_name}({expr_js})'
1056
+
1057
+ # Không có useState hoặc không phải $ variable → dùng arrow function thông thường
1058
+ # Kiểm tra xem có cần event parameter không
1059
+ if '@EVENT' in expr_js or 'event' in expr_js.lower():
1060
+ return f'(event) => {expr_js}'
1061
+ else:
1062
+ return f'() => {expr_js}'
1063
+
1064
+ def build_event_config_unified(self, event_type, handlers, arrow_functions):
1065
+ """
1066
+ Build event config với unified format: arrow functions + handler objects
1067
+ Tất cả đều dùng __addEventConfig
1068
+ Ví dụ: @click($a++; $b--, test($a, $b))
1069
+ → __addEventConfig('click', [() => a++, () => b--, {"handler": "test", "params": [a, b]}])
1070
+ """
1071
+ handler_items = []
1072
+
1073
+ # Add arrow functions TRƯỚC
1074
+ handler_items.extend(arrow_functions)
1075
+
1076
+ # Add handlers (objects) SAU
1077
+ for handler in handlers:
1078
+ handler_name = handler['handler']
1079
+ params = handler['params']
1080
+
1081
+ # Process parameters
1082
+ processed_params = []
1083
+ for param in params:
1084
+ processed_params.append(self.process_parameter(param))
1085
+
1086
+ # Build handler object
1087
+ params_str = ','.join(processed_params)
1088
+ handler_str = f'{{"handler":"{handler_name}","params":[{params_str}]}}'
1089
+ handler_items.append(handler_str)
1090
+
1091
+ handlers_str = f'[{",".join(handler_items)}]'
1092
+ return f'this.__addEventConfig("{event_type}", {handlers_str})'
1093
+
1094
+ def build_event_config_mixed(self, event_type, handlers, quick_handles):
1095
+ """
1096
+ Build event config với mixed format: quick_handles (arrow functions) + handlers (objects)
1097
+ DEPRECATED: Dùng build_event_config_unified thay thế
1098
+ """
1099
+ return self.build_event_config_unified(event_type, handlers, quick_handles)