jaclang 0.8.9__py3-none-any.whl → 0.8.10__py3-none-any.whl

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.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (103) hide show
  1. jaclang/cli/cli.py +147 -25
  2. jaclang/cli/cmdreg.py +144 -8
  3. jaclang/compiler/__init__.py +6 -1
  4. jaclang/compiler/codeinfo.py +16 -1
  5. jaclang/compiler/constant.py +33 -13
  6. jaclang/compiler/jac.lark +130 -31
  7. jaclang/compiler/larkparse/jac_parser.py +2 -2
  8. jaclang/compiler/parser.py +567 -176
  9. jaclang/compiler/passes/__init__.py +2 -1
  10. jaclang/compiler/passes/ast_gen/__init__.py +5 -0
  11. jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
  12. jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
  13. jaclang/compiler/passes/ecmascript/__init__.py +25 -0
  14. jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
  15. jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
  16. jaclang/compiler/passes/ecmascript/estree.py +972 -0
  17. jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
  18. jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
  19. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
  20. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
  21. jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
  22. jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
  23. jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
  24. jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
  25. jaclang/compiler/passes/main/__init__.py +0 -3
  26. jaclang/compiler/passes/main/annex_pass.py +23 -1
  27. jaclang/compiler/passes/main/pyast_gen_pass.py +324 -234
  28. jaclang/compiler/passes/main/pyast_load_pass.py +46 -11
  29. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
  30. jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
  31. jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
  32. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
  33. jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
  34. jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
  35. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
  36. jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -2
  37. jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
  38. jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
  39. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
  40. jaclang/compiler/passes/main/type_checker_pass.py +7 -0
  41. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +115 -0
  42. jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
  43. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
  44. jaclang/compiler/passes/transform.py +9 -1
  45. jaclang/compiler/passes/uni_pass.py +5 -7
  46. jaclang/compiler/program.py +22 -25
  47. jaclang/compiler/tests/test_client_codegen.py +113 -0
  48. jaclang/compiler/tests/test_importer.py +12 -10
  49. jaclang/compiler/tests/test_parser.py +249 -3
  50. jaclang/compiler/type_system/type_evaluator.jac +169 -50
  51. jaclang/compiler/type_system/type_utils.py +1 -1
  52. jaclang/compiler/type_system/types.py +6 -0
  53. jaclang/compiler/unitree.py +430 -84
  54. jaclang/langserve/engine.jac +224 -288
  55. jaclang/langserve/sem_manager.jac +12 -8
  56. jaclang/langserve/server.jac +48 -48
  57. jaclang/langserve/tests/fixtures/greet.py +17 -0
  58. jaclang/langserve/tests/fixtures/md_path.jac +22 -0
  59. jaclang/langserve/tests/fixtures/user.jac +15 -0
  60. jaclang/langserve/tests/test_server.py +66 -371
  61. jaclang/lib.py +1 -1
  62. jaclang/runtimelib/client_bundle.py +169 -0
  63. jaclang/runtimelib/client_runtime.jac +586 -0
  64. jaclang/runtimelib/constructs.py +2 -0
  65. jaclang/runtimelib/machine.py +259 -100
  66. jaclang/runtimelib/meta_importer.py +111 -22
  67. jaclang/runtimelib/mtp.py +15 -0
  68. jaclang/runtimelib/server.py +1089 -0
  69. jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
  70. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  71. jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
  72. jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
  73. jaclang/runtimelib/tests/test_client_bundle.py +55 -0
  74. jaclang/runtimelib/tests/test_client_render.py +63 -0
  75. jaclang/runtimelib/tests/test_serve.py +1069 -0
  76. jaclang/settings.py +0 -2
  77. jaclang/tests/fixtures/iife_functions.jac +142 -0
  78. jaclang/tests/fixtures/iife_functions_client.jac +143 -0
  79. jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
  80. jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
  81. jaclang/tests/fixtures/needs_import_dup.jac +6 -4
  82. jaclang/tests/fixtures/py_run.py +7 -5
  83. jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
  84. jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
  85. jaclang/tests/test_cli.py +1 -1
  86. jaclang/tests/test_language.py +10 -39
  87. jaclang/tests/test_reference.py +17 -2
  88. jaclang/utils/NonGPT.py +375 -0
  89. jaclang/utils/helpers.py +44 -16
  90. jaclang/utils/lang_tools.py +31 -4
  91. jaclang/utils/tests/test_lang_tools.py +1 -1
  92. jaclang/utils/treeprinter.py +8 -3
  93. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
  94. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/RECORD +96 -66
  95. jaclang/compiler/passes/main/binder_pass.py +0 -594
  96. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
  97. jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
  98. jaclang/langserve/tests/session.jac +0 -294
  99. jaclang/langserve/tests/test_dev_server.py +0 -80
  100. jaclang/runtimelib/importer.py +0 -351
  101. jaclang/tests/test_typecheck.py +0 -542
  102. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
  103. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,2068 @@
1
+ """ECMAScript AST Generation Pass for the Jac compiler.
2
+
3
+ This pass transforms the Jac AST into equivalent ECMAScript AST following
4
+ the ESTree specification by:
5
+
6
+ 1. Traversing the Jac AST and generating corresponding ESTree nodes
7
+ 2. Handling all Jac language constructs and translating them to JavaScript/ECMAScript equivalents:
8
+ - Classes, functions, and methods
9
+ - Control flow statements (if/else, loops, try/catch)
10
+ - Data structures (arrays, objects)
11
+ - Special Jac features (walkers, abilities, archetypes) converted to JS classes
12
+ - Data spatial operations converted to appropriate JS patterns
13
+
14
+ 3. Managing imports and module dependencies
15
+ 4. Preserving source location information
16
+ 5. Generating valid ECMAScript code that can be executed in JavaScript environments
17
+
18
+ The output of this pass is a complete ESTree AST representation that can be
19
+ serialized to JavaScript source code or used by JavaScript tooling.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ from dataclasses import dataclass, field
26
+ from typing import Any, Callable, Optional, Sequence, Union
27
+
28
+ import jaclang.compiler.passes.ecmascript.estree as es
29
+ import jaclang.compiler.unitree as uni
30
+ from jaclang.compiler.constant import Tokens as Tok
31
+ from jaclang.compiler.passes.ast_gen import BaseAstGenPass
32
+ from jaclang.compiler.passes.ast_gen.jsx_processor import EsJsxProcessor
33
+ from jaclang.compiler.passes.ecmascript.es_unparse import es_to_js
34
+
35
+ ES_LOGICAL_OPS: dict[Tok, str] = {Tok.KW_AND: "&&", Tok.KW_OR: "||"}
36
+
37
+ ES_BINARY_OPS: dict[Tok, str] = {
38
+ Tok.EE: "===",
39
+ Tok.NE: "!==",
40
+ Tok.LT: "<",
41
+ Tok.GT: ">",
42
+ Tok.LTE: "<=",
43
+ Tok.GTE: ">=",
44
+ Tok.PLUS: "+",
45
+ Tok.MINUS: "-",
46
+ Tok.STAR_MUL: "*",
47
+ Tok.DIV: "/",
48
+ Tok.MOD: "%",
49
+ Tok.BW_AND: "&",
50
+ Tok.BW_OR: "|",
51
+ Tok.BW_XOR: "^",
52
+ Tok.LSHIFT: "<<",
53
+ Tok.RSHIFT: ">>",
54
+ }
55
+
56
+ ES_COMPARISON_OPS: dict[Tok, str] = {
57
+ Tok.EE: "===",
58
+ Tok.NE: "!==",
59
+ Tok.LT: "<",
60
+ Tok.GT: ">",
61
+ Tok.LTE: "<=",
62
+ Tok.GTE: ">=",
63
+ Tok.KW_IN: "in",
64
+ Tok.KW_NIN: "in",
65
+ }
66
+
67
+ ES_UNARY_OPS: dict[Tok, str] = {
68
+ Tok.MINUS: "-",
69
+ Tok.PLUS: "+",
70
+ Tok.NOT: "!",
71
+ Tok.BW_NOT: "~",
72
+ }
73
+
74
+ ES_AUG_ASSIGN_OPS: dict[Tok, str] = {
75
+ Tok.ADD_EQ: "+=",
76
+ Tok.SUB_EQ: "-=",
77
+ Tok.MUL_EQ: "*=",
78
+ Tok.DIV_EQ: "/=",
79
+ Tok.MOD_EQ: "%=",
80
+ Tok.BW_AND_EQ: "&=",
81
+ Tok.BW_OR_EQ: "|=",
82
+ Tok.BW_XOR_EQ: "^=",
83
+ Tok.LSHIFT_EQ: "<<=",
84
+ Tok.RSHIFT_EQ: ">>=",
85
+ Tok.STAR_POW_EQ: "**=",
86
+ }
87
+
88
+
89
+ @dataclass
90
+ class ScopeInfo:
91
+ """Track declarations within a lexical scope."""
92
+
93
+ node: uni.UniScopeNode
94
+ declared: set[str] = field(default_factory=set)
95
+ hoisted: list[es.Statement] = field(default_factory=list)
96
+
97
+
98
+ @dataclass
99
+ class AssignmentTargetInfo:
100
+ """Container for processed assignment targets."""
101
+
102
+ node: uni.UniNode
103
+ left: Union[es.Pattern, es.Expression]
104
+ reference: Optional[es.Expression]
105
+ decl_name: Optional[str]
106
+ pattern_names: list[tuple[str, uni.Name]]
107
+ is_first: bool
108
+
109
+
110
+ class EsastGenPass(BaseAstGenPass[es.Statement]):
111
+ """Jac to ECMAScript AST transpilation pass."""
112
+
113
+ def before_pass(self) -> None:
114
+ """Initialize the pass."""
115
+ from jaclang.compiler.codeinfo import ClientManifest
116
+
117
+ self.child_passes: list[EsastGenPass] = self._init_child_passes(EsastGenPass)
118
+ self.imports: list[es.ImportDeclaration] = []
119
+ self.exports: list[es.ExportNamedDeclaration] = []
120
+ self.scope_stack: list[ScopeInfo] = []
121
+ self.scope_map: dict[uni.UniScopeNode, ScopeInfo] = {}
122
+ self.client_manifest = ClientManifest()
123
+ self.client_scope_stack: list[bool] = [] # Track client scope nesting
124
+ self.jsx_processor = EsJsxProcessor(self)
125
+
126
+ def enter_node(self, node: uni.UniNode) -> None:
127
+ """Enter node."""
128
+ if isinstance(node, uni.UniScopeNode):
129
+ self._push_scope(node)
130
+ if node.gen.es_ast:
131
+ self.prune()
132
+ return
133
+ super().enter_node(node)
134
+
135
+ def exit_node(self, node: uni.UniNode) -> None:
136
+ """Exit node."""
137
+ super().exit_node(node)
138
+ if isinstance(node, uni.UniScopeNode):
139
+ self._pop_scope(node)
140
+
141
+ def sync_loc(
142
+ self, es_node: es.Node, jac_node: Optional[uni.UniNode] = None
143
+ ) -> es.Node:
144
+ """Sync source locations from Jac node to ES node."""
145
+ if not jac_node:
146
+ jac_node = self.cur_node
147
+ es_node.loc = es.SourceLocation(
148
+ start=es.Position(
149
+ line=jac_node.loc.first_line, column=jac_node.loc.col_start
150
+ ),
151
+ end=es.Position(line=jac_node.loc.last_line, column=jac_node.loc.col_end),
152
+ )
153
+ return es_node
154
+
155
+ # Scope helpers
156
+ # =============
157
+
158
+ def _push_scope(self, node: uni.UniScopeNode) -> None:
159
+ """Enter a new lexical scope."""
160
+ info = ScopeInfo(node=node)
161
+ self.scope_stack.append(info)
162
+ self.scope_map[node] = info
163
+
164
+ def _pop_scope(self, node: uni.UniScopeNode) -> None:
165
+ """Exit a lexical scope."""
166
+ if self.scope_stack and self.scope_stack[-1].node is node:
167
+ self.scope_stack.pop()
168
+ self.scope_map.pop(node, None)
169
+
170
+ def _current_scope(self) -> Optional[ScopeInfo]:
171
+ """Get the scope currently being populated."""
172
+ return self.scope_stack[-1] if self.scope_stack else None
173
+
174
+ def _is_declared_in_current_scope(self, name: str) -> bool:
175
+ """Check if a name is already declared in the active scope."""
176
+ scope = self._current_scope()
177
+ return name in scope.declared if scope else False
178
+
179
+ def _register_declaration(self, name: str) -> None:
180
+ """Mark a name as declared within the current scope."""
181
+ scope = self._current_scope()
182
+ if scope:
183
+ scope.declared.add(name)
184
+
185
+ def _ensure_identifier_declared(self, name: str, jac_node: uni.UniNode) -> None:
186
+ """Hoist a declaration for identifiers introduced mid-expression (e.g., walrus)."""
187
+ scope = self._current_scope()
188
+ if not scope or name in scope.declared:
189
+ return
190
+ ident = self.sync_loc(es.Identifier(name=name), jac_node=jac_node)
191
+ declarator = self.sync_loc(
192
+ es.VariableDeclarator(id=ident, init=None), jac_node=jac_node
193
+ )
194
+ decl = self.sync_loc(
195
+ es.VariableDeclaration(declarations=[declarator], kind="let"),
196
+ jac_node=jac_node,
197
+ )
198
+ scope.hoisted.append(decl)
199
+ scope.declared.add(name)
200
+
201
+ def _prepend_hoisted(
202
+ self, node: uni.UniScopeNode, statements: list[es.Statement]
203
+ ) -> list[es.Statement]:
204
+ """Insert hoisted declarations, if any, ahead of the given statements."""
205
+ scope = self.scope_map.get(node)
206
+ if scope and scope.hoisted:
207
+ hoisted = list(scope.hoisted)
208
+ scope.hoisted.clear()
209
+ return hoisted + statements
210
+ return statements
211
+
212
+ def _is_in_client_scope(self) -> bool:
213
+ """Check if we're currently in a client-side function."""
214
+ return any(self.client_scope_stack)
215
+
216
+ def _collect_stmt_body(
217
+ self, body: Optional[Sequence[uni.UniNode]]
218
+ ) -> list[es.Statement]:
219
+ """Convert a sequence of Jac statements into ESTree statements."""
220
+ if not body:
221
+ return []
222
+
223
+ statements: list[es.Statement] = []
224
+ for stmt in body:
225
+ if isinstance(stmt, uni.Semi):
226
+ continue
227
+ generated = getattr(stmt.gen, "es_ast", None)
228
+ if isinstance(generated, list):
229
+ statements.extend(
230
+ item for item in generated if isinstance(item, es.Statement)
231
+ )
232
+ elif isinstance(generated, es.Statement):
233
+ statements.append(generated)
234
+ return statements
235
+
236
+ def _get_ast_or_default(
237
+ self,
238
+ node: Optional[uni.UniNode],
239
+ default_factory: Callable[[Optional[uni.UniNode]], es.Node],
240
+ ) -> es.Node:
241
+ """Return an existing ESTree node or synthesize a fallback."""
242
+ if node and getattr(node.gen, "es_ast", None):
243
+ generated = node.gen.es_ast
244
+ if isinstance(generated, es.Node):
245
+ return generated
246
+ fallback = default_factory(node)
247
+ jac_ref = node if node is not None else self.cur_node
248
+ return self.sync_loc(fallback, jac_node=jac_ref)
249
+
250
+ def _build_block_statement(
251
+ self,
252
+ scope_node: uni.UniScopeNode,
253
+ body_nodes: Optional[Sequence[uni.UniNode]],
254
+ ) -> es.BlockStatement:
255
+ """Construct a block statement from a Jac scope node."""
256
+ statements = self._collect_stmt_body(body_nodes)
257
+ statements = self._prepend_hoisted(scope_node, statements)
258
+ return self.sync_loc(es.BlockStatement(body=statements), jac_node=scope_node)
259
+
260
+ # Module and Program
261
+ # ==================
262
+
263
+ def exit_module(self, node: uni.Module) -> None:
264
+ """Process module node."""
265
+ body: list[Union[es.Statement, es.ModuleDeclaration]] = []
266
+
267
+ # Add imports
268
+ body.extend(self.imports)
269
+
270
+ # Insert hoisted declarations (e.g., walrus-introduced identifiers)
271
+ scope = self.scope_map.get(node)
272
+ if scope and scope.hoisted:
273
+ hoisted = list(scope.hoisted)
274
+ scope.hoisted.clear()
275
+ body.extend(hoisted)
276
+
277
+ merged_body = self._merge_module_bodies(node)
278
+
279
+ # Process module body
280
+ client_items: list[Union[es.Statement, list[es.Statement]]] = []
281
+ fallback_items: list[Union[es.Statement, list[es.Statement]]] = []
282
+ for stmt in merged_body:
283
+ if stmt.gen.es_ast:
284
+ target_list = (
285
+ client_items
286
+ if getattr(stmt, "is_client_decl", False)
287
+ else fallback_items
288
+ )
289
+ target_list.append(stmt.gen.es_ast)
290
+ target_body = client_items if client_items else fallback_items
291
+ body.extend(self._flatten_ast_list(target_body))
292
+
293
+ # Add exports
294
+ body.extend(self.exports)
295
+
296
+ program = self.sync_loc(
297
+ es.Program(body=body, sourceType="module"), jac_node=node
298
+ )
299
+ node.gen.es_ast = program
300
+
301
+ # Generate JavaScript code from ES AST
302
+ node.gen.js = es_to_js(node.gen.es_ast)
303
+
304
+ # Sort and assign client manifest
305
+ self.client_manifest.exports.sort()
306
+ self.client_manifest.globals.sort()
307
+ node.gen.client_manifest = self.client_manifest
308
+
309
+ def exit_sub_tag(self, node: uni.SubTag[uni.T]) -> None:
310
+ """Process SubTag node."""
311
+ if node.tag.gen.es_ast:
312
+ node.gen.es_ast = node.tag.gen.es_ast
313
+
314
+ # Import/Export Statements
315
+ # ========================
316
+
317
+ def exit_import(self, node: uni.Import) -> None:
318
+ """Process import statement."""
319
+ if node.from_loc and node.items:
320
+ source = self.sync_loc(
321
+ es.Literal(value=node.from_loc.dot_path_str), jac_node=node.from_loc
322
+ )
323
+ specifiers: list[
324
+ Union[
325
+ es.ImportSpecifier,
326
+ es.ImportDefaultSpecifier,
327
+ es.ImportNamespaceSpecifier,
328
+ ]
329
+ ] = []
330
+
331
+ for item in node.items:
332
+ if isinstance(item, uni.ModuleItem):
333
+ imported = self.sync_loc(
334
+ es.Identifier(name=item.name.sym_name), jac_node=item.name
335
+ )
336
+ local = self.sync_loc(
337
+ es.Identifier(
338
+ name=(
339
+ item.alias.sym_name
340
+ if item.alias
341
+ else item.name.sym_name
342
+ )
343
+ ),
344
+ jac_node=item.alias if item.alias else item.name,
345
+ )
346
+ specifiers.append(
347
+ self.sync_loc(
348
+ es.ImportSpecifier(imported=imported, local=local),
349
+ jac_node=item,
350
+ )
351
+ )
352
+
353
+ import_decl = self.sync_loc(
354
+ es.ImportDeclaration(specifiers=specifiers, source=source),
355
+ jac_node=node,
356
+ )
357
+ self.imports.append(import_decl)
358
+ node.gen.es_ast = [] # Imports are added to module level
359
+
360
+ def exit_module_path(self, node: uni.ModulePath) -> None:
361
+ """Process module path."""
362
+ node.gen.es_ast = None
363
+
364
+ def exit_module_item(self, node: uni.ModuleItem) -> None:
365
+ """Process module item."""
366
+ node.gen.es_ast = None
367
+
368
+ # Declarations
369
+ # ============
370
+
371
+ def exit_archetype(self, node: uni.Archetype) -> None:
372
+ """Process archetype (class) declaration."""
373
+ if getattr(node, "is_client_decl", False):
374
+ self.client_manifest.has_client = True
375
+ self.client_manifest.exports.append(node.name.sym_name)
376
+
377
+ body_stmts: list[
378
+ Union[es.MethodDefinition, es.PropertyDefinition, es.StaticBlock]
379
+ ] = []
380
+ has_members: list[uni.ArchHas] = []
381
+
382
+ inner = self._get_body_inner(node)
383
+
384
+ if inner:
385
+ for stmt in inner:
386
+ if isinstance(stmt, uni.ArchHas):
387
+ has_members.append(stmt)
388
+ continue
389
+ if (
390
+ stmt.gen.es_ast
391
+ and stmt.gen.es_ast
392
+ and isinstance(
393
+ stmt.gen.es_ast,
394
+ (es.MethodDefinition, es.PropertyDefinition, es.StaticBlock),
395
+ )
396
+ ):
397
+ body_stmts.append(stmt.gen.es_ast)
398
+
399
+ if node.arch_type.name == Tok.KW_OBJECT and has_members:
400
+ constructor_stmts: list[es.Statement] = []
401
+ props_param = self.sync_loc(
402
+ es.AssignmentPattern(
403
+ left=self.sync_loc(es.Identifier(name="props"), jac_node=node),
404
+ right=self.sync_loc(
405
+ es.ObjectExpression(properties=[]), jac_node=node
406
+ ),
407
+ ),
408
+ jac_node=node,
409
+ )
410
+
411
+ for arch_has in has_members:
412
+ if arch_has.is_static:
413
+ for var in arch_has.vars:
414
+ default_expr = (
415
+ var.value.gen.es_ast
416
+ if var.value
417
+ and var.value.gen.es_ast
418
+ and var.value.gen.es_ast
419
+ else self.sync_loc(es.Literal(value=None), jac_node=var)
420
+ )
421
+ static_prop = self.sync_loc(
422
+ es.PropertyDefinition(
423
+ key=self.sync_loc(
424
+ es.Identifier(name=var.name.sym_name),
425
+ jac_node=var.name,
426
+ ),
427
+ value=default_expr,
428
+ static=True,
429
+ ),
430
+ jac_node=var,
431
+ )
432
+ body_stmts.append(static_prop)
433
+ continue
434
+
435
+ for var in arch_has.vars:
436
+ props_ident = self.sync_loc(
437
+ es.Identifier(name="props"), jac_node=var
438
+ )
439
+ prop_ident = self.sync_loc(
440
+ es.Identifier(name=var.name.sym_name), jac_node=var.name
441
+ )
442
+ this_member = self.sync_loc(
443
+ es.MemberExpression(
444
+ object=self.sync_loc(es.ThisExpression(), jac_node=var),
445
+ property=prop_ident,
446
+ computed=False,
447
+ ),
448
+ jac_node=var,
449
+ )
450
+ props_access = self.sync_loc(
451
+ es.MemberExpression(
452
+ object=props_ident,
453
+ property=self.sync_loc(
454
+ es.Identifier(name=var.name.sym_name),
455
+ jac_node=var.name,
456
+ ),
457
+ computed=False,
458
+ ),
459
+ jac_node=var,
460
+ )
461
+ has_call = self.sync_loc(
462
+ es.CallExpression(
463
+ callee=self.sync_loc(
464
+ es.MemberExpression(
465
+ object=props_ident,
466
+ property=self.sync_loc(
467
+ es.Identifier(name="hasOwnProperty"),
468
+ jac_node=var,
469
+ ),
470
+ computed=False,
471
+ ),
472
+ jac_node=var,
473
+ ),
474
+ arguments=[
475
+ self.sync_loc(
476
+ es.Literal(value=var.name.sym_name),
477
+ jac_node=var.name,
478
+ )
479
+ ],
480
+ ),
481
+ jac_node=var,
482
+ )
483
+ default_expr = (
484
+ var.value.gen.es_ast
485
+ if var.value and var.value.gen.es_ast and var.value.gen.es_ast
486
+ else self.sync_loc(es.Literal(value=None), jac_node=var)
487
+ )
488
+ conditional = self.sync_loc(
489
+ es.ConditionalExpression(
490
+ test=has_call,
491
+ consequent=props_access,
492
+ alternate=default_expr,
493
+ ),
494
+ jac_node=var,
495
+ )
496
+ assignment = self.sync_loc(
497
+ es.AssignmentExpression(
498
+ operator="=", left=this_member, right=conditional
499
+ ),
500
+ jac_node=var,
501
+ )
502
+ constructor_stmts.append(
503
+ self.sync_loc(
504
+ es.ExpressionStatement(expression=assignment),
505
+ jac_node=var,
506
+ )
507
+ )
508
+
509
+ if constructor_stmts:
510
+ constructor_method = self.sync_loc(
511
+ es.MethodDefinition(
512
+ key=self.sync_loc(
513
+ es.Identifier(name="constructor"), jac_node=node
514
+ ),
515
+ value=self.sync_loc(
516
+ es.FunctionExpression(
517
+ id=None,
518
+ params=[props_param],
519
+ body=self.sync_loc(
520
+ es.BlockStatement(body=constructor_stmts),
521
+ jac_node=node,
522
+ ),
523
+ ),
524
+ jac_node=node,
525
+ ),
526
+ kind="constructor",
527
+ static=False,
528
+ ),
529
+ jac_node=node,
530
+ )
531
+ body_stmts.insert(0, constructor_method)
532
+
533
+ # Create class body
534
+ class_body = self.sync_loc(es.ClassBody(body=body_stmts), jac_node=node)
535
+
536
+ # Handle base classes
537
+ super_class: Optional[es.Expression] = None
538
+ if node.base_classes:
539
+ base = node.base_classes[0]
540
+ if base.gen.es_ast:
541
+ super_class = base.gen.es_ast
542
+
543
+ # Create class declaration
544
+ class_id = self.sync_loc(
545
+ es.Identifier(name=node.name.sym_name), jac_node=node.name
546
+ )
547
+
548
+ class_decl = self.sync_loc(
549
+ es.ClassDeclaration(id=class_id, superClass=super_class, body=class_body),
550
+ jac_node=node,
551
+ )
552
+
553
+ node.gen.es_ast = class_decl
554
+
555
+ def exit_enum(self, node: uni.Enum) -> None:
556
+ """Process enum declaration as an object."""
557
+ properties: list[es.Property] = []
558
+
559
+ inner = self._get_body_inner(node)
560
+
561
+ if inner:
562
+ for stmt in inner:
563
+ if isinstance(stmt, uni.Assignment):
564
+ for target in stmt.target:
565
+ if isinstance(target, uni.AstSymbolNode):
566
+ key = self.sync_loc(
567
+ es.Identifier(name=target.sym_name), jac_node=target
568
+ )
569
+ value: es.Expression
570
+ if stmt.value and stmt.value.gen.es_ast:
571
+ value = stmt.value.gen.es_ast
572
+ else:
573
+ value = self.sync_loc(
574
+ es.Literal(value=None), jac_node=stmt
575
+ )
576
+ prop = self.sync_loc(
577
+ es.Property(key=key, value=value, kind="init"),
578
+ jac_node=stmt,
579
+ )
580
+ properties.append(prop)
581
+
582
+ # Create as const variable with object
583
+ obj_expr = self.sync_loc(
584
+ es.ObjectExpression(properties=properties), jac_node=node
585
+ )
586
+ var_id = self.sync_loc(
587
+ es.Identifier(name=node.name.sym_name), jac_node=node.name
588
+ )
589
+ var_decl = self.sync_loc(
590
+ es.VariableDeclaration(
591
+ declarations=[
592
+ self.sync_loc(
593
+ es.VariableDeclarator(id=var_id, init=obj_expr), jac_node=node
594
+ )
595
+ ],
596
+ kind="const",
597
+ ),
598
+ jac_node=node,
599
+ )
600
+
601
+ node.gen.es_ast = var_decl
602
+
603
+ def enter_ability(self, node: uni.Ability) -> None:
604
+ """Track entry into ability to manage client scope."""
605
+ # Push True if this is a client function, False otherwise
606
+ is_client = getattr(node, "is_client_decl", False)
607
+ self.client_scope_stack.append(is_client)
608
+
609
+ def exit_ability(self, node: uni.Ability) -> None:
610
+ """Process ability (function/method) declaration."""
611
+ if getattr(node, "is_client_decl", False) and not node.is_method:
612
+ self.client_manifest.has_client = True
613
+ name = node.name_ref.sym_name
614
+ self.client_manifest.exports.append(name)
615
+ self.client_manifest.params[name] = (
616
+ [p.name.sym_name for p in node.signature.params if hasattr(p, "name")]
617
+ if isinstance(node.signature, uni.FuncSignature)
618
+ else []
619
+ )
620
+
621
+ params: list[es.Pattern] = []
622
+ if isinstance(node.signature, uni.FuncSignature):
623
+ for param in node.signature.params:
624
+ if param.gen.es_ast:
625
+ params.append(param.gen.es_ast)
626
+
627
+ # Process body
628
+ inner = self._get_body_inner(node)
629
+ body_stmts = self._collect_stmt_body(inner)
630
+ body_stmts = self._prepend_hoisted(node, body_stmts)
631
+ block = self.sync_loc(es.BlockStatement(body=body_stmts), jac_node=node)
632
+
633
+ func_id = self.sync_loc(
634
+ es.Identifier(name=node.name_ref.sym_name), jac_node=node.name_ref
635
+ )
636
+
637
+ # Check if this is a method (has parent archetype)
638
+ if node.is_method:
639
+ # Create method definition
640
+ func_expr = self.sync_loc(
641
+ es.FunctionExpression(
642
+ id=None, params=params, body=block, async_=node.is_async
643
+ ),
644
+ jac_node=node,
645
+ )
646
+ method_def = self.sync_loc(
647
+ es.MethodDefinition(
648
+ key=func_id, value=func_expr, kind="method", static=node.is_static
649
+ ),
650
+ jac_node=node,
651
+ )
652
+ node.gen.es_ast = method_def
653
+ else:
654
+ # Create function declaration
655
+ func_decl = self.sync_loc(
656
+ es.FunctionDeclaration(
657
+ id=func_id, params=params, body=block, async_=node.is_async
658
+ ),
659
+ jac_node=node,
660
+ )
661
+ node.gen.es_ast = func_decl
662
+
663
+ # Pop the client scope stack
664
+ if self.client_scope_stack:
665
+ self.client_scope_stack.pop()
666
+
667
+ def exit_func_signature(self, node: uni.FuncSignature) -> None:
668
+ """Process function signature."""
669
+ node.gen.es_ast = None
670
+
671
+ def exit_param_var(self, node: uni.ParamVar) -> None:
672
+ """Process parameter variable."""
673
+ param_id = self.sync_loc(
674
+ es.Identifier(name=node.name.sym_name), jac_node=node.name
675
+ )
676
+ self._register_declaration(param_id.name)
677
+ node.gen.es_ast = param_id
678
+
679
+ def exit_arch_has(self, node: uni.ArchHas) -> None:
680
+ """Process class field declarations."""
681
+ # ES doesn't directly support field declarations in the same way
682
+ # This could be handled via constructor assignments
683
+ node.gen.es_ast = None
684
+
685
+ def exit_has_var(self, node: uni.HasVar) -> None:
686
+ """Process has variable."""
687
+ node.gen.es_ast = None
688
+
689
+ # JSX Nodes
690
+ # =========
691
+
692
+ def exit_jsx_element(self, node: uni.JsxElement) -> None:
693
+ """Process JSX element."""
694
+ node.gen.es_ast = self.jsx_processor.element(node)
695
+
696
+ def exit_jsx_element_name(self, node: uni.JsxElementName) -> None:
697
+ """Process JSX element name."""
698
+ self.jsx_processor.element_name(node)
699
+
700
+ def exit_jsx_spread_attribute(self, node: uni.JsxSpreadAttribute) -> None:
701
+ """Process JSX spread attribute."""
702
+ self.jsx_processor.spread_attribute(node)
703
+
704
+ def exit_jsx_normal_attribute(self, node: uni.JsxNormalAttribute) -> None:
705
+ """Process JSX normal attribute."""
706
+ self.jsx_processor.normal_attribute(node)
707
+
708
+ def exit_jsx_text(self, node: uni.JsxText) -> None:
709
+ """Process JSX text node."""
710
+ self.jsx_processor.text(node)
711
+
712
+ def exit_jsx_expression(self, node: uni.JsxExpression) -> None:
713
+ """Process JSX expression child."""
714
+ self.jsx_processor.expression(node)
715
+
716
+ # Control Flow Statements
717
+ # =======================
718
+
719
+ def exit_if_stmt(self, node: uni.IfStmt) -> None:
720
+ """Process if statement."""
721
+ test = self._get_ast_or_default(
722
+ node.condition, default_factory=lambda _src: es.Literal(value=True)
723
+ )
724
+ consequent = self._build_block_statement(node, node.body)
725
+
726
+ alternate: Optional[es.Statement] = None
727
+ if node.else_body and node.else_body.gen.es_ast:
728
+ alternate = node.else_body.gen.es_ast
729
+
730
+ if_stmt = self.sync_loc(
731
+ es.IfStatement(test=test, consequent=consequent, alternate=alternate),
732
+ jac_node=node,
733
+ )
734
+ node.gen.es_ast = if_stmt
735
+
736
+ def exit_else_if(self, node: uni.ElseIf) -> None:
737
+ """Process else-if clause."""
738
+ test = self._get_ast_or_default(
739
+ node.condition, default_factory=lambda _src: es.Literal(value=True)
740
+ )
741
+ consequent = self._build_block_statement(node, node.body)
742
+
743
+ alternate: Optional[es.Statement] = None
744
+ if node.else_body and node.else_body.gen.es_ast:
745
+ alternate = node.else_body.gen.es_ast
746
+
747
+ if_stmt = self.sync_loc(
748
+ es.IfStatement(test=test, consequent=consequent, alternate=alternate),
749
+ jac_node=node,
750
+ )
751
+ node.gen.es_ast = if_stmt
752
+
753
+ def exit_else_stmt(self, node: uni.ElseStmt) -> None:
754
+ """Process else clause."""
755
+ stmts = self._collect_stmt_body(node.body)
756
+ stmts = self._prepend_hoisted(node, stmts)
757
+ block = self.sync_loc(es.BlockStatement(body=stmts), jac_node=node)
758
+ node.gen.es_ast = block
759
+
760
+ def exit_while_stmt(self, node: uni.WhileStmt) -> None:
761
+ """Process while statement."""
762
+ test = self._get_ast_or_default(
763
+ node.condition, default_factory=lambda _src: es.Literal(value=True)
764
+ )
765
+ body = self._build_block_statement(node, node.body)
766
+
767
+ while_stmt = self.sync_loc(
768
+ es.WhileStatement(test=test, body=body), jac_node=node
769
+ )
770
+ node.gen.es_ast = while_stmt
771
+
772
+ def exit_in_for_stmt(self, node: uni.InForStmt) -> None:
773
+ """Process for-in statement."""
774
+ left = (
775
+ node.target.gen.es_ast
776
+ if node.target.gen.es_ast
777
+ else self.sync_loc(es.Identifier(name="item"), jac_node=node.target)
778
+ )
779
+ right = (
780
+ node.collection.gen.es_ast
781
+ if node.collection.gen.es_ast
782
+ else self.sync_loc(
783
+ es.Identifier(name="collection"), jac_node=node.collection
784
+ )
785
+ )
786
+
787
+ body = self._build_block_statement(node, node.body)
788
+
789
+ pattern_nodes = (
790
+ es.Identifier,
791
+ es.ArrayPattern,
792
+ es.ObjectPattern,
793
+ es.AssignmentPattern,
794
+ es.RestElement,
795
+ )
796
+ if isinstance(left, es.VariableDeclaration):
797
+ decl = left
798
+ else:
799
+ if isinstance(left, pattern_nodes):
800
+ pattern = left
801
+ else:
802
+ pattern = self.sync_loc(
803
+ es.Identifier(name="_item"), jac_node=node.target
804
+ )
805
+ declarator = self.sync_loc(
806
+ es.VariableDeclarator(id=pattern, init=None), jac_node=node.target
807
+ )
808
+ decl = self.sync_loc(
809
+ es.VariableDeclaration(
810
+ declarations=[declarator],
811
+ kind="const",
812
+ ),
813
+ jac_node=node.target,
814
+ )
815
+
816
+ # Use for-of for iteration over values
817
+ for_stmt = self.sync_loc(
818
+ es.ForOfStatement(left=decl, right=right, body=body, await_=node.is_async),
819
+ jac_node=node,
820
+ )
821
+ node.gen.es_ast = for_stmt
822
+
823
+ def exit_try_stmt(self, node: uni.TryStmt) -> None:
824
+ """Process try statement."""
825
+ block = self._build_block_statement(node, node.body)
826
+
827
+ handler: Optional[es.CatchClause] = None
828
+ if node.excepts:
829
+ # Take first except clause
830
+ except_node = node.excepts[0]
831
+ if except_node.gen.es_ast:
832
+ handler = except_node.gen.es_ast
833
+
834
+ finalizer: Optional[es.BlockStatement] = None
835
+ if (
836
+ node.finally_body
837
+ and node.finally_body.gen.es_ast
838
+ and isinstance(node.finally_body.gen.es_ast, es.BlockStatement)
839
+ ):
840
+ finalizer = node.finally_body.gen.es_ast
841
+
842
+ try_stmt = self.sync_loc(
843
+ es.TryStatement(block=block, handler=handler, finalizer=finalizer),
844
+ jac_node=node,
845
+ )
846
+ node.gen.es_ast = try_stmt
847
+
848
+ def exit_except(self, node: uni.Except) -> None:
849
+ """Process except clause."""
850
+ param: Optional[es.Pattern] = None
851
+ if node.name:
852
+ param = self.sync_loc(
853
+ es.Identifier(name=node.name.sym_name), jac_node=node.name
854
+ )
855
+
856
+ body = self._build_block_statement(node, node.body)
857
+
858
+ catch_clause = self.sync_loc(
859
+ es.CatchClause(param=param, body=body), jac_node=node
860
+ )
861
+ node.gen.es_ast = catch_clause
862
+
863
+ def exit_finally_stmt(self, node: uni.FinallyStmt) -> None:
864
+ """Process finally clause."""
865
+ node.gen.es_ast = self._build_block_statement(node, node.body)
866
+
867
+ def exit_raise_stmt(self, node: uni.RaiseStmt) -> None:
868
+ """Process raise statement."""
869
+ argument = (
870
+ node.cause.gen.es_ast
871
+ if node.cause and node.cause.gen.es_ast
872
+ else self.sync_loc(es.Identifier(name="Error"), jac_node=node)
873
+ )
874
+
875
+ if isinstance(argument, es.CallExpression):
876
+ callee = argument.callee
877
+ if isinstance(callee, es.Identifier) and callee.name in {
878
+ "Exception",
879
+ "Error",
880
+ }:
881
+ new_expr = self.sync_loc(
882
+ es.NewExpression(
883
+ callee=self.sync_loc(
884
+ es.Identifier(name="Error"), jac_node=node
885
+ ),
886
+ arguments=argument.arguments,
887
+ ),
888
+ jac_node=node,
889
+ )
890
+ argument = new_expr
891
+
892
+ throw_stmt = self.sync_loc(es.ThrowStatement(argument=argument), jac_node=node)
893
+ node.gen.es_ast = throw_stmt
894
+
895
+ def exit_assert_stmt(self, node: uni.AssertStmt) -> None:
896
+ """Process assert statement as if-throw."""
897
+ test = (
898
+ node.condition.gen.es_ast
899
+ if node.condition.gen.es_ast
900
+ else self.sync_loc(es.Literal(value=True), jac_node=node.condition)
901
+ )
902
+
903
+ # Negate the test (throw if condition is false)
904
+ negated_test = self.sync_loc(
905
+ es.UnaryExpression(operator="!", prefix=True, argument=test), jac_node=node
906
+ )
907
+
908
+ error_msg = "Assertion failed"
909
+ if (
910
+ node.error_msg
911
+ and node.error_msg.gen.es_ast
912
+ and isinstance(node.error_msg.gen.es_ast, es.Literal)
913
+ ):
914
+ error_msg = str(node.error_msg.gen.es_ast.value)
915
+
916
+ throw_stmt = self.sync_loc(
917
+ es.ThrowStatement(
918
+ argument=self.sync_loc(
919
+ es.NewExpression(
920
+ callee=self.sync_loc(
921
+ es.Identifier(name="Error"), jac_node=node
922
+ ),
923
+ arguments=[
924
+ self.sync_loc(es.Literal(value=error_msg), jac_node=node)
925
+ ],
926
+ ),
927
+ jac_node=node,
928
+ )
929
+ ),
930
+ jac_node=node,
931
+ )
932
+
933
+ if_stmt = self.sync_loc(
934
+ es.IfStatement(
935
+ test=negated_test,
936
+ consequent=self.sync_loc(
937
+ es.BlockStatement(body=[throw_stmt]), jac_node=node
938
+ ),
939
+ ),
940
+ jac_node=node,
941
+ )
942
+ node.gen.es_ast = if_stmt
943
+
944
+ def exit_return_stmt(self, node: uni.ReturnStmt) -> None:
945
+ """Process return statement."""
946
+ argument: Optional[es.Expression] = None
947
+ if node.expr and node.expr.gen.es_ast:
948
+ argument = node.expr.gen.es_ast
949
+
950
+ ret_stmt = self.sync_loc(es.ReturnStatement(argument=argument), jac_node=node)
951
+ node.gen.es_ast = ret_stmt
952
+
953
+ def exit_ctrl_stmt(self, node: uni.CtrlStmt) -> None:
954
+ """Process control statement (break/continue)."""
955
+ if node.ctrl.name == Tok.KW_BREAK:
956
+ stmt = self.sync_loc(es.BreakStatement(), jac_node=node)
957
+ else: # continue
958
+ stmt = self.sync_loc(es.ContinueStatement(), jac_node=node)
959
+ node.gen.es_ast = stmt
960
+
961
+ def exit_expr_stmt(self, node: uni.ExprStmt) -> None:
962
+ """Process expression statement."""
963
+ expr = self._get_ast_or_default(
964
+ node.expr, default_factory=lambda _src: es.Literal(value=None)
965
+ )
966
+
967
+ expr_stmt = self.sync_loc(
968
+ es.ExpressionStatement(expression=expr), jac_node=node
969
+ )
970
+ node.gen.es_ast = expr_stmt
971
+
972
+ # Expressions
973
+ # ===========
974
+
975
+ def exit_binary_expr(self, node: uni.BinaryExpr) -> None:
976
+ """Process binary expression."""
977
+ left = self._get_ast_or_default(
978
+ node.left,
979
+ default_factory=lambda src: (
980
+ es.Identifier(name=src.sym_name)
981
+ if isinstance(src, uni.Name)
982
+ else es.Literal(value=0)
983
+ ),
984
+ )
985
+ right = self._get_ast_or_default(
986
+ node.right,
987
+ default_factory=lambda src: (
988
+ es.Identifier(name=src.sym_name)
989
+ if isinstance(src, uni.Name)
990
+ else es.Literal(value=0)
991
+ ),
992
+ )
993
+
994
+ op_name = getattr(node.op, "name", None)
995
+
996
+ if op_name == Tok.KW_SPAWN:
997
+ spawn_call = self.sync_loc(
998
+ es.CallExpression(
999
+ callee=self.sync_loc(
1000
+ es.Identifier(name="__jacSpawn"), jac_node=node
1001
+ ),
1002
+ arguments=[left, right],
1003
+ ),
1004
+ jac_node=node,
1005
+ )
1006
+ node.gen.es_ast = spawn_call
1007
+ return
1008
+
1009
+ if op_name == Tok.WALRUS_EQ and isinstance(left, es.Identifier):
1010
+ self._ensure_identifier_declared(left.name, node.left)
1011
+ assign_expr = self.sync_loc(
1012
+ es.AssignmentExpression(operator="=", left=left, right=right),
1013
+ jac_node=node,
1014
+ )
1015
+ node.gen.es_ast = assign_expr
1016
+ return
1017
+
1018
+ logical_op = ES_LOGICAL_OPS.get(op_name)
1019
+ if logical_op:
1020
+ bin_expr = self.sync_loc(
1021
+ es.LogicalExpression(operator=logical_op, left=left, right=right),
1022
+ jac_node=node,
1023
+ )
1024
+ else:
1025
+ operator = ES_BINARY_OPS.get(op_name, "+")
1026
+ bin_expr = self.sync_loc(
1027
+ es.BinaryExpression(operator=operator, left=left, right=right),
1028
+ jac_node=node,
1029
+ )
1030
+
1031
+ node.gen.es_ast = bin_expr
1032
+
1033
+ def exit_bool_expr(self, node: uni.BoolExpr) -> None:
1034
+ """Process boolean expression (and/or)."""
1035
+ # BoolExpr has op and list of values
1036
+ if not node.values or len(node.values) < 2:
1037
+ node.gen.es_ast = self.sync_loc(es.Literal(value=None), jac_node=node)
1038
+ return
1039
+
1040
+ logical_op = ES_LOGICAL_OPS.get(node.op.name, "&&")
1041
+
1042
+ # Build the logical expression from left to right
1043
+ result = self._get_ast_or_default(
1044
+ node.values[0], default_factory=lambda _src: es.Literal(value=None)
1045
+ )
1046
+
1047
+ for val in node.values[1:]:
1048
+ right = self._get_ast_or_default(
1049
+ val, default_factory=lambda _src: es.Literal(value=None)
1050
+ )
1051
+ result = self.sync_loc(
1052
+ es.LogicalExpression(operator=logical_op, left=result, right=right),
1053
+ jac_node=node,
1054
+ )
1055
+
1056
+ node.gen.es_ast = result
1057
+
1058
+ def exit_compare_expr(self, node: uni.CompareExpr) -> None:
1059
+ """Process compare expression."""
1060
+ # CompareExpr can have multiple comparisons chained: a < b < c
1061
+ # Need to convert to: a < b && b < c
1062
+
1063
+ if not node.rights or not node.ops:
1064
+ # Fallback to simple comparison
1065
+ node.gen.es_ast = self.sync_loc(es.Literal(value=True), jac_node=node)
1066
+ return
1067
+
1068
+ # Build comparisons
1069
+ comparisons: list[es.Expression] = []
1070
+ left = self._get_ast_or_default(
1071
+ node.left,
1072
+ default_factory=lambda src: (
1073
+ es.Identifier(name=src.sym_name)
1074
+ if isinstance(src, uni.Name)
1075
+ else es.Identifier(name="left")
1076
+ ),
1077
+ )
1078
+
1079
+ for op_token, right_node in zip(node.ops, node.rights):
1080
+ right = self._get_ast_or_default(
1081
+ right_node,
1082
+ default_factory=lambda src: (
1083
+ es.Identifier(name=src.sym_name)
1084
+ if isinstance(src, uni.Name)
1085
+ else es.Identifier(name="right")
1086
+ ),
1087
+ )
1088
+ operator = ES_COMPARISON_OPS.get(op_token.name, "===")
1089
+
1090
+ if op_token.name == Tok.KW_NIN:
1091
+ in_expr = self.sync_loc(
1092
+ es.BinaryExpression(operator="in", left=left, right=right),
1093
+ jac_node=node,
1094
+ )
1095
+ comparison = self.sync_loc(
1096
+ es.UnaryExpression(operator="!", prefix=True, argument=in_expr),
1097
+ jac_node=node,
1098
+ )
1099
+ else:
1100
+ comparison = self.sync_loc(
1101
+ es.BinaryExpression(operator=operator, left=left, right=right),
1102
+ jac_node=node,
1103
+ )
1104
+
1105
+ comparisons.append(comparison)
1106
+ left = right
1107
+
1108
+ # Combine with && if multiple comparisons
1109
+ if len(comparisons) == 1:
1110
+ node.gen.es_ast = comparisons[0]
1111
+ else:
1112
+ result = comparisons[0]
1113
+ for comp in comparisons[1:]:
1114
+ result = self.sync_loc(
1115
+ es.LogicalExpression(operator="&&", left=result, right=comp),
1116
+ jac_node=node,
1117
+ )
1118
+ node.gen.es_ast = result
1119
+
1120
+ def exit_unary_expr(self, node: uni.UnaryExpr) -> None:
1121
+ """Process unary expression."""
1122
+ operand = self._get_ast_or_default(
1123
+ node.operand, default_factory=lambda _src: es.Literal(value=0)
1124
+ )
1125
+
1126
+ operator = ES_UNARY_OPS.get(node.op.name, "!")
1127
+
1128
+ unary_expr = self.sync_loc(
1129
+ es.UnaryExpression(operator=operator, prefix=True, argument=operand),
1130
+ jac_node=node,
1131
+ )
1132
+ node.gen.es_ast = unary_expr
1133
+
1134
+ def _convert_assignment_target(
1135
+ self, target: uni.UniNode
1136
+ ) -> tuple[
1137
+ Union[es.Pattern, es.Expression], Optional[es.Expression], Optional[str]
1138
+ ]:
1139
+ """Convert a Jac assignment target into an ESTree pattern/expression."""
1140
+ if isinstance(target, uni.Name):
1141
+ identifier = self.sync_loc(
1142
+ es.Identifier(name=target.sym_name), jac_node=target
1143
+ )
1144
+ return identifier, identifier, target.sym_name
1145
+
1146
+ if isinstance(target, (uni.TupleVal, uni.ListVal)):
1147
+ elements: list[Optional[es.Pattern]] = []
1148
+ for value in getattr(target, "values", []):
1149
+ if value is None:
1150
+ elements.append(None)
1151
+ continue
1152
+ pattern, _, _ = self._convert_assignment_target(value)
1153
+ elements.append(pattern if isinstance(pattern, es.Pattern) else pattern)
1154
+ pattern = self.sync_loc(es.ArrayPattern(elements=elements), jac_node=target)
1155
+ return pattern, None, None
1156
+
1157
+ if isinstance(target, uni.DictVal):
1158
+ properties: list[es.AssignmentProperty] = []
1159
+ for kv in target.kv_pairs:
1160
+ if not isinstance(kv, uni.KVPair) or kv.key is None:
1161
+ continue
1162
+ key_expr = (
1163
+ kv.key.gen.es_ast
1164
+ if kv.key.gen.es_ast
1165
+ else self.sync_loc(es.Identifier(name="key"), jac_node=kv.key)
1166
+ )
1167
+ value_pattern, _, _ = self._convert_assignment_target(kv.value)
1168
+ assignment = self.sync_loc(
1169
+ es.AssignmentProperty(
1170
+ key=key_expr,
1171
+ value=(
1172
+ value_pattern
1173
+ if isinstance(value_pattern, es.Pattern)
1174
+ else value_pattern
1175
+ ),
1176
+ shorthand=False,
1177
+ ),
1178
+ jac_node=kv,
1179
+ )
1180
+ properties.append(assignment)
1181
+ pattern = self.sync_loc(
1182
+ es.ObjectPattern(properties=properties), jac_node=target
1183
+ )
1184
+ return pattern, None, None
1185
+
1186
+ if isinstance(target, uni.SubTag):
1187
+ return self._convert_assignment_target(target.tag)
1188
+
1189
+ left = (
1190
+ target.gen.es_ast
1191
+ if target.gen.es_ast
1192
+ else self.sync_loc(es.Identifier(name="temp"), jac_node=target)
1193
+ )
1194
+ reference = left if isinstance(left, es.Expression) else None
1195
+ return left, reference, None
1196
+
1197
+ def _collect_pattern_names(self, target: uni.UniNode) -> list[tuple[str, uni.Name]]:
1198
+ """Collect identifier names from a (possibly nested) destructuring target."""
1199
+ names: list[tuple[str, uni.Name]] = []
1200
+ if isinstance(target, uni.Name):
1201
+ names.append((target.sym_name, target))
1202
+ elif isinstance(target, (uni.TupleVal, uni.ListVal)):
1203
+ for value in getattr(target, "values", []):
1204
+ names.extend(self._collect_pattern_names(value))
1205
+ elif isinstance(target, uni.DictVal):
1206
+ for kv in target.kv_pairs:
1207
+ if isinstance(kv, uni.KVPair):
1208
+ names.extend(self._collect_pattern_names(kv.value))
1209
+ elif isinstance(target, uni.SubTag):
1210
+ names.extend(self._collect_pattern_names(target.tag))
1211
+ return names
1212
+
1213
+ def _is_name_first_definition(self, name_node: uni.Name) -> bool:
1214
+ """Determine whether a name node corresponds to the first definition in its scope."""
1215
+ sym = getattr(name_node, "sym", None)
1216
+ if sym and name_node.name_spec in sym.defn:
1217
+ return sym.defn.index(name_node.name_spec) == 0
1218
+ return True
1219
+
1220
+ def exit_assignment(self, node: uni.Assignment) -> None:
1221
+ """Process assignment expression."""
1222
+ if not node.target:
1223
+ node.gen.es_ast = None
1224
+ return
1225
+
1226
+ value_expr = (
1227
+ node.value.gen.es_ast if node.value and node.value.gen.es_ast else None
1228
+ )
1229
+
1230
+ if node.aug_op:
1231
+ left, _, _ = self._convert_assignment_target(node.target[0])
1232
+ operator = ES_AUG_ASSIGN_OPS.get(node.aug_op.name, "=")
1233
+ right = value_expr or self._get_ast_or_default(
1234
+ node.value, default_factory=lambda _src: es.Identifier(name="undefined")
1235
+ )
1236
+ assign_expr = self.sync_loc(
1237
+ es.AssignmentExpression(operator=operator, left=left, right=right),
1238
+ jac_node=node,
1239
+ )
1240
+ expr_stmt = self.sync_loc(
1241
+ es.ExpressionStatement(expression=assign_expr), jac_node=node
1242
+ )
1243
+ node.gen.es_ast = expr_stmt
1244
+ return
1245
+
1246
+ targets_info: list[AssignmentTargetInfo] = []
1247
+ for target_node in node.target:
1248
+ left, reference, decl_name = self._convert_assignment_target(target_node)
1249
+ pattern_names = self._collect_pattern_names(target_node)
1250
+ first_def = False
1251
+ if isinstance(target_node, uni.Name):
1252
+ first_def = self._is_name_first_definition(target_node)
1253
+ elif pattern_names:
1254
+ first_def = any(
1255
+ self._is_name_first_definition(name_node)
1256
+ for _, name_node in pattern_names
1257
+ )
1258
+
1259
+ targets_info.append(
1260
+ AssignmentTargetInfo(
1261
+ node=target_node,
1262
+ left=left,
1263
+ reference=reference,
1264
+ decl_name=decl_name,
1265
+ pattern_names=pattern_names,
1266
+ is_first=first_def,
1267
+ )
1268
+ )
1269
+
1270
+ statements: list[es.Statement] = []
1271
+ current_value = value_expr or self._get_ast_or_default(
1272
+ node.value, default_factory=lambda _src: es.Identifier(name="undefined")
1273
+ )
1274
+
1275
+ for info in reversed(targets_info):
1276
+ target_node = info.node
1277
+ left = info.left
1278
+ decl_name = info.decl_name
1279
+ pattern_names = info.pattern_names
1280
+ is_first = info.is_first
1281
+
1282
+ should_declare = False
1283
+ if decl_name:
1284
+ should_declare = is_first and not self._is_declared_in_current_scope(
1285
+ decl_name
1286
+ )
1287
+ elif pattern_names:
1288
+ should_declare = any(
1289
+ self._is_name_first_definition(name_node)
1290
+ and not self._is_declared_in_current_scope(name)
1291
+ for name, name_node in pattern_names
1292
+ )
1293
+
1294
+ if should_declare:
1295
+ declarator = self.sync_loc(
1296
+ es.VariableDeclarator(
1297
+ id=left, init=current_value if value_expr is not None else None
1298
+ ),
1299
+ jac_node=target_node,
1300
+ )
1301
+ decl_stmt = self.sync_loc(
1302
+ es.VariableDeclaration(
1303
+ declarations=[declarator],
1304
+ kind="let",
1305
+ ),
1306
+ jac_node=target_node,
1307
+ )
1308
+ statements.append(decl_stmt)
1309
+
1310
+ if decl_name:
1311
+ self._register_declaration(decl_name)
1312
+ else:
1313
+ for name, _ in pattern_names:
1314
+ self._register_declaration(name)
1315
+ else:
1316
+ assign_expr = self.sync_loc(
1317
+ es.AssignmentExpression(
1318
+ operator="=",
1319
+ left=left,
1320
+ right=current_value,
1321
+ ),
1322
+ jac_node=target_node,
1323
+ )
1324
+ expr_stmt = self.sync_loc(
1325
+ es.ExpressionStatement(expression=assign_expr),
1326
+ jac_node=target_node,
1327
+ )
1328
+ statements.append(expr_stmt)
1329
+
1330
+ if isinstance(left, es.Identifier):
1331
+ current_value = self.sync_loc(
1332
+ es.Identifier(name=left.name), jac_node=target_node
1333
+ )
1334
+ elif isinstance(info.reference, es.Identifier):
1335
+ ref_ident = info.reference
1336
+ current_value = self.sync_loc(
1337
+ es.Identifier(name=ref_ident.name), jac_node=target_node
1338
+ )
1339
+ else:
1340
+ current_value = info.reference or current_value
1341
+
1342
+ if len(statements) == 1:
1343
+ node.gen.es_ast = statements[0]
1344
+ else:
1345
+ node.gen.es_ast = statements
1346
+
1347
+ def exit_func_call(self, node: uni.FuncCall) -> None:
1348
+ """Process function call."""
1349
+ # Special case: type(x) -> typeof x in JavaScript
1350
+ # Check the target directly before processing it into an es_ast
1351
+ target_is_type = False
1352
+ if isinstance(node.target, (uni.Name, uni.BuiltinType)):
1353
+ target_name = getattr(node.target, "sym_name", None)
1354
+ if target_name == "type":
1355
+ target_is_type = True
1356
+
1357
+ args: list[Union[es.Expression, es.SpreadElement]] = []
1358
+ for param in node.params:
1359
+ if param.gen.es_ast:
1360
+ args.append(param.gen.es_ast)
1361
+
1362
+ if target_is_type and len(args) == 1 and isinstance(args[0], es.Expression):
1363
+ typeof_expr = self.sync_loc(
1364
+ es.UnaryExpression(operator="typeof", prefix=True, argument=args[0]),
1365
+ jac_node=node,
1366
+ )
1367
+ node.gen.es_ast = typeof_expr
1368
+ return
1369
+
1370
+ # Check if we're calling a server function from client code
1371
+ # This should transform the call to use __jacCallFunction
1372
+ if self._is_in_client_scope() and isinstance(node.target, uni.Name):
1373
+ target_sym = getattr(node.target, "sym", None)
1374
+ if target_sym and target_sym.defn:
1375
+ # Get the definition node
1376
+ defn_node = target_sym.defn[0]
1377
+ # Check if it's an Ability (function) and if it's NOT a client function
1378
+ if hasattr(defn_node, "parent") and isinstance(
1379
+ defn_node.parent, uni.Ability
1380
+ ):
1381
+ ability_node = defn_node.parent
1382
+ is_server_func = not getattr(ability_node, "is_client_decl", False)
1383
+
1384
+ if is_server_func:
1385
+ # Transform to __jacCallFunction call
1386
+ func_name = node.target.sym_name
1387
+
1388
+ # Build parameter mapping
1389
+ param_names: list[str] = []
1390
+ if isinstance(ability_node.signature, uni.FuncSignature):
1391
+ param_names = [
1392
+ p.name.sym_name
1393
+ for p in ability_node.signature.params
1394
+ if hasattr(p, "name")
1395
+ ]
1396
+
1397
+ # Build args object {param1: arg1, param2: arg2, ...}
1398
+ props: list[Union[es.Property, es.SpreadElement]] = []
1399
+ for i, arg in enumerate(args):
1400
+ if isinstance(arg, es.SpreadElement):
1401
+ # Handle spread arguments
1402
+ props.append(arg)
1403
+ elif i < len(param_names):
1404
+ # Named parameter
1405
+ key = self.sync_loc(
1406
+ es.Literal(value=param_names[i]), jac_node=node
1407
+ )
1408
+ props.append(
1409
+ self.sync_loc(
1410
+ es.Property(
1411
+ key=key,
1412
+ value=arg,
1413
+ kind="init",
1414
+ method=False,
1415
+ shorthand=False,
1416
+ computed=False,
1417
+ ),
1418
+ jac_node=node,
1419
+ )
1420
+ )
1421
+
1422
+ args_obj = self.sync_loc(
1423
+ es.ObjectExpression(properties=props), jac_node=node
1424
+ )
1425
+
1426
+ # Create __jacCallFunction(func_name, args_obj) call
1427
+ call_expr = self.sync_loc(
1428
+ es.AwaitExpression(
1429
+ argument=self.sync_loc(
1430
+ es.CallExpression(
1431
+ callee=self.sync_loc(
1432
+ es.Identifier(name="__jacCallFunction"),
1433
+ jac_node=node,
1434
+ ),
1435
+ arguments=[
1436
+ self.sync_loc(
1437
+ es.Literal(value=func_name),
1438
+ jac_node=node,
1439
+ ),
1440
+ args_obj,
1441
+ ],
1442
+ ),
1443
+ jac_node=node,
1444
+ )
1445
+ ),
1446
+ jac_node=node,
1447
+ )
1448
+ node.gen.es_ast = call_expr
1449
+ return
1450
+
1451
+ callee = (
1452
+ node.target.gen.es_ast
1453
+ if node.target.gen.es_ast
1454
+ else self.sync_loc(es.Identifier(name="func"), jac_node=node.target)
1455
+ )
1456
+
1457
+ if isinstance(callee, es.MemberExpression) and isinstance(
1458
+ callee.property, es.Identifier
1459
+ ):
1460
+ method_map = {
1461
+ "lower": "toLowerCase",
1462
+ "upper": "toUpperCase",
1463
+ "startswith": "startsWith",
1464
+ "endswith": "endsWith",
1465
+ }
1466
+ replacement = method_map.get(callee.property.name)
1467
+ if replacement:
1468
+ callee = self.sync_loc(
1469
+ es.MemberExpression(
1470
+ object=callee.object,
1471
+ property=self.sync_loc(
1472
+ es.Identifier(name=replacement), jac_node=node
1473
+ ),
1474
+ computed=callee.computed,
1475
+ ),
1476
+ jac_node=node,
1477
+ )
1478
+
1479
+ call_expr = self.sync_loc(
1480
+ es.CallExpression(callee=callee, arguments=args), jac_node=node
1481
+ )
1482
+ node.gen.es_ast = call_expr
1483
+
1484
+ def exit_index_slice(self, node: uni.IndexSlice) -> None:
1485
+ """Process index/slice - just store the slice info, actual member access is handled by AtomTrailer."""
1486
+ # IndexSlice doesn't have a target - it's used within an AtomTrailer
1487
+ # Store the slice information for use by the parent AtomTrailer
1488
+ if node.slices and len(node.slices) > 0:
1489
+ first_slice = node.slices[0]
1490
+ if node.is_range:
1491
+ # Store slice info - will be used by AtomTrailer
1492
+ node.gen.es_ast = {
1493
+ "type": "slice",
1494
+ "start": (
1495
+ first_slice.start.gen.es_ast
1496
+ if first_slice.start and first_slice.start.gen.es_ast
1497
+ else None
1498
+ ),
1499
+ "stop": (
1500
+ first_slice.stop.gen.es_ast
1501
+ if first_slice.stop and first_slice.stop.gen.es_ast
1502
+ else None
1503
+ ),
1504
+ }
1505
+ else:
1506
+ # Store index info - will be used by AtomTrailer
1507
+ node.gen.es_ast = {
1508
+ "type": "index",
1509
+ "value": (
1510
+ first_slice.start.gen.es_ast
1511
+ if first_slice.start and first_slice.start.gen.es_ast
1512
+ else self.sync_loc(es.Literal(value=0), jac_node=node)
1513
+ ),
1514
+ }
1515
+ else:
1516
+ node.gen.es_ast = None
1517
+
1518
+ def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
1519
+ """Process attribute access."""
1520
+ obj = (
1521
+ node.target.gen.es_ast
1522
+ if node.target.gen.es_ast
1523
+ else self.sync_loc(es.Identifier(name="obj"), jac_node=node.target)
1524
+ )
1525
+
1526
+ if node.right and node.right.gen.es_ast:
1527
+ # The right side is already processed (could be a call, etc.)
1528
+ # Check if it's a Name that needs to become a property access
1529
+ if isinstance(node.right, uni.Name):
1530
+ prop = self.sync_loc(
1531
+ es.Identifier(name=node.right.sym_name), jac_node=node.right
1532
+ )
1533
+ member_expr = self.sync_loc(
1534
+ es.MemberExpression(object=obj, property=prop, computed=False),
1535
+ jac_node=node,
1536
+ )
1537
+ node.gen.es_ast = member_expr
1538
+ elif isinstance(node.right, uni.IndexSlice):
1539
+ # Handle index/slice operations
1540
+ slice_info = node.right.gen.es_ast
1541
+ if isinstance(slice_info, dict):
1542
+ if slice_info.get("type") == "slice":
1543
+ # Slice operation - convert to .slice() call
1544
+ start = slice_info.get("start") or self.sync_loc(
1545
+ es.Literal(value=0), jac_node=node
1546
+ )
1547
+ stop = slice_info.get("stop")
1548
+ args: list[es.Expression] = [start]
1549
+ if stop is not None:
1550
+ args.append(stop)
1551
+ slice_call = self.sync_loc(
1552
+ es.CallExpression(
1553
+ callee=self.sync_loc(
1554
+ es.MemberExpression(
1555
+ object=obj,
1556
+ property=self.sync_loc(
1557
+ es.Identifier(name="slice"), jac_node=node
1558
+ ),
1559
+ computed=False,
1560
+ ),
1561
+ jac_node=node,
1562
+ ),
1563
+ arguments=args,
1564
+ ),
1565
+ jac_node=node,
1566
+ )
1567
+ node.gen.es_ast = slice_call
1568
+ elif slice_info.get("type") == "index":
1569
+ # Index operation
1570
+ idx = slice_info.get("value") or self.sync_loc(
1571
+ es.Literal(value=0), jac_node=node
1572
+ )
1573
+ member_expr = self.sync_loc(
1574
+ es.MemberExpression(
1575
+ object=obj, property=idx, computed=True
1576
+ ),
1577
+ jac_node=node,
1578
+ )
1579
+ node.gen.es_ast = member_expr
1580
+ else:
1581
+ node.gen.es_ast = obj
1582
+ else:
1583
+ node.gen.es_ast = obj
1584
+ else:
1585
+ # If right is a call or other expression, it should already be processed
1586
+ node.gen.es_ast = node.right.gen.es_ast
1587
+
1588
+ def exit_lambda_expr(self, node: uni.LambdaExpr) -> None:
1589
+ """Process lambda expression as arrow function."""
1590
+ # Extract parameters
1591
+ params: list[es.Pattern] = []
1592
+ if isinstance(node.signature, uni.FuncSignature):
1593
+ for param in node.signature.params:
1594
+ if param.gen.es_ast:
1595
+ params.append(param.gen.es_ast)
1596
+
1597
+ # Check if body is a code block or single expression
1598
+ if isinstance(node.body, list):
1599
+ # Multi-statement lambda: use arrow function with block body
1600
+ body_stmts: list[es.Statement] = []
1601
+ for stmt in node.body:
1602
+ if stmt.gen.es_ast:
1603
+ if isinstance(stmt.gen.es_ast, list):
1604
+ body_stmts.extend(stmt.gen.es_ast)
1605
+ else:
1606
+ body_stmts.append(stmt.gen.es_ast)
1607
+
1608
+ body_stmts = self._prepend_hoisted(node, body_stmts)
1609
+ block_stmt = self.sync_loc(
1610
+ es.BlockStatement(body=body_stmts), jac_node=node
1611
+ )
1612
+
1613
+ arrow_func = self.sync_loc(
1614
+ es.ArrowFunctionExpression(
1615
+ params=params, body=block_stmt, async_=False
1616
+ ),
1617
+ jac_node=node,
1618
+ )
1619
+ node.gen.es_ast = arrow_func
1620
+ else:
1621
+ # Single expression lambda: use arrow function with expression body
1622
+ body_expr = (
1623
+ node.body.gen.es_ast
1624
+ if node.body.gen.es_ast
1625
+ else self.sync_loc(es.Literal(value=None), jac_node=node.body)
1626
+ )
1627
+
1628
+ arrow_func = self.sync_loc(
1629
+ es.ArrowFunctionExpression(params=params, body=body_expr, async_=False),
1630
+ jac_node=node,
1631
+ )
1632
+ node.gen.es_ast = arrow_func
1633
+
1634
+ def exit_atom_unit(self, node: uni.AtomUnit) -> None:
1635
+ """Process parenthesized atom."""
1636
+ # Check if this is an IIFE (Immediately Invoked Function Expression)
1637
+ # i.e., a parenthesized function_decl (Ability)
1638
+ if isinstance(node.value, uni.Ability) and node.value.gen.es_ast:
1639
+ # Convert function declaration to function expression for IIFE
1640
+ func_decl = node.value.gen.es_ast
1641
+ if isinstance(func_decl, es.FunctionDeclaration):
1642
+ # Convert to function expression
1643
+ func_expr = self.sync_loc(
1644
+ es.FunctionExpression(
1645
+ id=func_decl.id,
1646
+ params=func_decl.params,
1647
+ body=func_decl.body,
1648
+ async_=func_decl.async_,
1649
+ ),
1650
+ jac_node=node.value,
1651
+ )
1652
+ node.gen.es_ast = func_expr
1653
+ else:
1654
+ node.gen.es_ast = node.value.gen.es_ast
1655
+ elif node.value and node.value.gen.es_ast:
1656
+ node.gen.es_ast = node.value.gen.es_ast
1657
+ else:
1658
+ node.gen.es_ast = self.sync_loc(es.Literal(value=None), jac_node=node)
1659
+
1660
+ def exit_list_val(self, node: uni.ListVal) -> None:
1661
+ """Process list literal."""
1662
+ elements: list[Optional[Union[es.Expression, es.SpreadElement]]] = []
1663
+ for item in node.values:
1664
+ if item.gen.es_ast:
1665
+ elements.append(item.gen.es_ast)
1666
+
1667
+ array_expr = self.sync_loc(es.ArrayExpression(elements=elements), jac_node=node)
1668
+ node.gen.es_ast = array_expr
1669
+
1670
+ def exit_set_val(self, node: uni.SetVal) -> None:
1671
+ """Process set literal as new Set()."""
1672
+ elements: list[Union[es.Expression, es.SpreadElement]] = []
1673
+ for item in node.values:
1674
+ if item.gen.es_ast:
1675
+ elements.append(item.gen.es_ast)
1676
+
1677
+ # Create new Set([...])
1678
+ set_expr = self.sync_loc(
1679
+ es.NewExpression(
1680
+ callee=self.sync_loc(es.Identifier(name="Set"), jac_node=node),
1681
+ arguments=[
1682
+ self.sync_loc(es.ArrayExpression(elements=elements), jac_node=node)
1683
+ ],
1684
+ ),
1685
+ jac_node=node,
1686
+ )
1687
+ node.gen.es_ast = set_expr
1688
+
1689
+ def exit_tuple_val(self, node: uni.TupleVal) -> None:
1690
+ """Process tuple as array."""
1691
+ elements: list[Optional[Union[es.Expression, es.SpreadElement]]] = []
1692
+ for item in node.values:
1693
+ if item.gen.es_ast:
1694
+ elements.append(item.gen.es_ast)
1695
+
1696
+ array_expr = self.sync_loc(es.ArrayExpression(elements=elements), jac_node=node)
1697
+ node.gen.es_ast = array_expr
1698
+
1699
+ def exit_dict_val(self, node: uni.DictVal) -> None:
1700
+ """Process dictionary literal."""
1701
+ properties: list[Union[es.Property, es.SpreadElement]] = []
1702
+ for kv_pair in node.kv_pairs:
1703
+ if not isinstance(kv_pair, uni.KVPair) or kv_pair.value is None:
1704
+ continue
1705
+
1706
+ if kv_pair.key is None:
1707
+ if kv_pair.value.gen.es_ast:
1708
+ properties.append(
1709
+ self.sync_loc(
1710
+ es.SpreadElement(argument=kv_pair.value.gen.es_ast),
1711
+ jac_node=kv_pair.value,
1712
+ )
1713
+ )
1714
+ continue
1715
+
1716
+ key = (
1717
+ kv_pair.key.gen.es_ast
1718
+ if kv_pair.key.gen.es_ast
1719
+ else self.sync_loc(es.Literal(value="key"), jac_node=kv_pair.key)
1720
+ )
1721
+ value = (
1722
+ kv_pair.value.gen.es_ast
1723
+ if kv_pair.value.gen.es_ast
1724
+ else self.sync_loc(es.Literal(value=None), jac_node=kv_pair.value)
1725
+ )
1726
+
1727
+ prop = self.sync_loc(
1728
+ es.Property(key=key, value=value, kind="init"), jac_node=kv_pair
1729
+ )
1730
+ properties.append(prop)
1731
+
1732
+ obj_expr = self.sync_loc(
1733
+ es.ObjectExpression(properties=properties), jac_node=node
1734
+ )
1735
+ node.gen.es_ast = obj_expr
1736
+
1737
+ def exit_k_v_pair(self, node: uni.KVPair) -> None:
1738
+ """Process key-value pair."""
1739
+ # Handled in dict_val
1740
+ pass
1741
+
1742
+ def exit_inner_compr(self, node: uni.InnerCompr) -> None:
1743
+ """Process list comprehension."""
1744
+ # List comprehensions need to be converted to functional style
1745
+ # [x for x in list] -> list.map(x => x)
1746
+ # This is a simplified version
1747
+ node.gen.es_ast = self.sync_loc(es.ArrayExpression(elements=[]), jac_node=node)
1748
+
1749
+ # Literals and Atoms
1750
+ # ==================
1751
+
1752
+ def exit_bool(self, node: uni.Bool) -> None:
1753
+ """Process boolean literal."""
1754
+ value = node.value.lower() == "true"
1755
+ raw_value = "true" if value else "false"
1756
+ bool_lit = self.sync_loc(es.Literal(value=value, raw=raw_value), jac_node=node)
1757
+ node.gen.es_ast = bool_lit
1758
+
1759
+ def exit_int(self, node: uni.Int) -> None:
1760
+ """Process integer literal."""
1761
+ # Use base 0 to auto-detect binary (0b), octal (0o), hex (0x), or decimal
1762
+ int_lit = self.sync_loc(
1763
+ es.Literal(value=int(node.value, 0), raw=node.value), jac_node=node
1764
+ )
1765
+ node.gen.es_ast = int_lit
1766
+
1767
+ def exit_float(self, node: uni.Float) -> None:
1768
+ """Process float literal."""
1769
+ float_lit = self.sync_loc(
1770
+ es.Literal(value=float(node.value), raw=node.value), jac_node=node
1771
+ )
1772
+ node.gen.es_ast = float_lit
1773
+
1774
+ def exit_multi_string(self, node: uni.MultiString) -> None:
1775
+ """Process multi-string literal."""
1776
+ # MultiString can contain multiple string parts (for concatenation)
1777
+ # For now, concatenate them into a single string
1778
+ if not node.strings:
1779
+ null_lit = self.sync_loc(es.Literal(value="", raw='""'), jac_node=node)
1780
+ node.gen.es_ast = null_lit
1781
+ return
1782
+
1783
+ # If single string, just use it
1784
+ if len(node.strings) == 1:
1785
+ string_node = node.strings[0]
1786
+ if string_node.gen.es_ast:
1787
+ node.gen.es_ast = string_node.gen.es_ast
1788
+ else:
1789
+ # Fallback: process the string directly (String only, not FString)
1790
+ if isinstance(string_node, uni.String):
1791
+ value = string_node.value
1792
+ if value.startswith(('"""', "'''")):
1793
+ value = value[3:-3]
1794
+ elif value.startswith(('"', "'")):
1795
+ value = value[1:-1]
1796
+ str_lit = self.sync_loc(
1797
+ es.Literal(value=value, raw=string_node.value),
1798
+ jac_node=string_node,
1799
+ )
1800
+ node.gen.es_ast = str_lit
1801
+ else:
1802
+ # FString should have been processed already
1803
+ node.gen.es_ast = self.sync_loc(es.Literal(value=""), jac_node=node)
1804
+ return
1805
+
1806
+ # Multiple strings - create a concatenation expression
1807
+ parts = []
1808
+ for string_node in node.strings:
1809
+ if string_node.gen.es_ast:
1810
+ parts.append(string_node.gen.es_ast)
1811
+ elif isinstance(string_node, uni.String):
1812
+ # Fallback for String nodes only
1813
+ value = string_node.value
1814
+ if value.startswith(('"""', "'''")):
1815
+ value = value[3:-3]
1816
+ elif value.startswith(('"', "'")):
1817
+ value = value[1:-1]
1818
+ raw_val = (
1819
+ json.dumps(value) if isinstance(value, str) else string_node.value
1820
+ )
1821
+ str_lit = self.sync_loc(
1822
+ es.Literal(value=value, raw=raw_val), jac_node=string_node
1823
+ )
1824
+ parts.append(str_lit)
1825
+ # Skip FString nodes that haven't been processed
1826
+
1827
+ if not parts:
1828
+ node.gen.es_ast = self.sync_loc(es.Literal(value=""), jac_node=node)
1829
+ return
1830
+
1831
+ # Create binary expression for concatenation
1832
+ result = parts[0]
1833
+ for part in parts[1:]:
1834
+ result = self.sync_loc(
1835
+ es.BinaryExpression(operator="+", left=result, right=part),
1836
+ jac_node=node,
1837
+ )
1838
+ node.gen.es_ast = result
1839
+
1840
+ def exit_string(self, node: uni.String) -> None:
1841
+ """Process string literal."""
1842
+ # Remove quotes from the value
1843
+ value = node.value
1844
+ if value.startswith(('"""', "'''")):
1845
+ value = value[3:-3]
1846
+ elif value.startswith(('"', "'")):
1847
+ value = value[1:-1]
1848
+
1849
+ raw_value = node.value
1850
+ if isinstance(value, str):
1851
+ raw_value = json.dumps(value)
1852
+
1853
+ str_lit = self.sync_loc(es.Literal(value=value, raw=raw_value), jac_node=node)
1854
+ node.gen.es_ast = str_lit
1855
+
1856
+ def exit_f_string(self, node: uni.FString) -> None:
1857
+ """Process f-string literal as template literal."""
1858
+ # F-strings need to be converted to template literals (backtick strings) in JS
1859
+ # f"Hello {name}" -> `Hello ${name}`
1860
+
1861
+ # For now, convert to concatenation of strings and expressions
1862
+ # This is a simplified version - proper template literals would be better
1863
+ parts: list[es.Expression] = []
1864
+
1865
+ for part in node.parts:
1866
+ if part.gen.es_ast:
1867
+ expr = part.gen.es_ast
1868
+ if isinstance(expr, es.ExpressionStatement):
1869
+ expr = expr.expression
1870
+ parts.append(expr)
1871
+
1872
+ if not parts:
1873
+ # Empty f-string
1874
+ node.gen.es_ast = self.sync_loc(es.Literal(value=""), jac_node=node)
1875
+ elif len(parts) == 1:
1876
+ # Single part
1877
+ node.gen.es_ast = parts[0]
1878
+ else:
1879
+ # Multiple parts - concatenate with +
1880
+ result = parts[0]
1881
+ for part in parts[1:]:
1882
+ result = self.sync_loc(
1883
+ es.BinaryExpression(operator="+", left=result, right=part),
1884
+ jac_node=node,
1885
+ )
1886
+ node.gen.es_ast = result
1887
+
1888
+ def exit_if_else_expr(self, node: uni.IfElseExpr) -> None:
1889
+ """Process ternary expression."""
1890
+ test = (
1891
+ node.condition.gen.es_ast
1892
+ if node.condition.gen.es_ast
1893
+ else self.sync_loc(es.Identifier(name="condition"), jac_node=node.condition)
1894
+ )
1895
+ consequent = (
1896
+ node.value.gen.es_ast
1897
+ if node.value.gen.es_ast
1898
+ else self.sync_loc(es.Identifier(name="value"), jac_node=node.value)
1899
+ )
1900
+ alternate = (
1901
+ node.else_value.gen.es_ast
1902
+ if node.else_value.gen.es_ast
1903
+ else self.sync_loc(
1904
+ es.Identifier(name="alternate"), jac_node=node.else_value
1905
+ )
1906
+ )
1907
+ cond_expr = self.sync_loc(
1908
+ es.ConditionalExpression(
1909
+ test=test, consequent=consequent, alternate=alternate
1910
+ ),
1911
+ jac_node=node,
1912
+ )
1913
+ node.gen.es_ast = cond_expr
1914
+
1915
+ def exit_await_expr(self, node: uni.AwaitExpr) -> None:
1916
+ """Process await expression."""
1917
+ argument = (
1918
+ node.target.gen.es_ast
1919
+ if node.target.gen.es_ast
1920
+ else self.sync_loc(es.Identifier(name="undefined"), jac_node=node.target)
1921
+ )
1922
+ await_expr = self.sync_loc(es.AwaitExpression(argument=argument), jac_node=node)
1923
+ node.gen.es_ast = await_expr
1924
+
1925
+ def exit_null(self, node: uni.Null) -> None:
1926
+ """Process null/None literal."""
1927
+ null_lit = self.sync_loc(es.Literal(value=None, raw="null"), jac_node=node)
1928
+ node.gen.es_ast = null_lit
1929
+
1930
+ def exit_name(self, node: uni.Name) -> None:
1931
+ """Process name/identifier."""
1932
+ # Map Python/Jac names to JS equivalents
1933
+ name_map = {
1934
+ "None": "null",
1935
+ "True": "true",
1936
+ "False": "false",
1937
+ "self": "this",
1938
+ }
1939
+
1940
+ name = name_map.get(node.sym_name, node.sym_name)
1941
+ identifier = self.sync_loc(es.Identifier(name=name), jac_node=node)
1942
+ node.gen.es_ast = identifier
1943
+
1944
+ # Special Statements
1945
+ # ==================
1946
+
1947
+ def exit_global_vars(self, node: uni.GlobalVars) -> None:
1948
+ """Process global variables."""
1949
+ if getattr(node, "is_client_decl", False):
1950
+ self.client_manifest.has_client = True
1951
+ for assignment in node.assignments:
1952
+ for target in assignment.target:
1953
+ if sym_name := getattr(target, "sym_name", None):
1954
+ self.client_manifest.globals.append(sym_name)
1955
+ if (
1956
+ assignment.value
1957
+ and (lit_val := self._literal_value(assignment.value))
1958
+ is not None
1959
+ ):
1960
+ self.client_manifest.globals_values[sym_name] = lit_val
1961
+
1962
+ statements: list[es.Statement] = []
1963
+ for assignment in node.assignments:
1964
+ if assignment.gen.es_ast:
1965
+ stmt = assignment.gen.es_ast
1966
+ if (
1967
+ isinstance(stmt, es.VariableDeclaration)
1968
+ and node.is_frozen
1969
+ and stmt.kind != "const"
1970
+ ):
1971
+ stmt.kind = "const"
1972
+ statements.append(stmt)
1973
+ node.gen.es_ast = statements
1974
+
1975
+ def exit_non_local_vars(self, node: uni.NonLocalVars) -> None:
1976
+ """Process non-local variables."""
1977
+ # Non-local doesn't have direct equivalent in ES
1978
+ node.gen.es_ast = []
1979
+
1980
+ def exit_module_code(self, node: uni.ModuleCode) -> None:
1981
+ """Process module code (with entry block)."""
1982
+ # Generate the body statements directly
1983
+ body_stmts: list[es.Statement] = []
1984
+ if node.body:
1985
+ for stmt in node.body:
1986
+ if stmt.gen.es_ast:
1987
+ if isinstance(stmt.gen.es_ast, list):
1988
+ body_stmts.extend(stmt.gen.es_ast)
1989
+ else:
1990
+ body_stmts.append(stmt.gen.es_ast)
1991
+
1992
+ # Module code is executed at module level, so just output the statements
1993
+ node.gen.es_ast = body_stmts
1994
+
1995
+ def exit_test(self, node: uni.Test) -> None:
1996
+ """Process test as a function."""
1997
+ # Convert test to a regular function
1998
+ params: list[es.Pattern] = []
1999
+
2000
+ body_stmts: list[es.Statement] = []
2001
+ if node.body:
2002
+ for stmt in node.body:
2003
+ if stmt.gen.es_ast:
2004
+ if isinstance(stmt.gen.es_ast, list):
2005
+ body_stmts.extend(stmt.gen.es_ast)
2006
+ else:
2007
+ body_stmts.append(stmt.gen.es_ast)
2008
+
2009
+ body_stmts = self._prepend_hoisted(node, body_stmts)
2010
+ block = self.sync_loc(es.BlockStatement(body=body_stmts), jac_node=node)
2011
+
2012
+ func_id = self.sync_loc(
2013
+ es.Identifier(name=node.name.sym_name), jac_node=node.name
2014
+ )
2015
+
2016
+ func_decl = self.sync_loc(
2017
+ es.FunctionDeclaration(id=func_id, params=params, body=block),
2018
+ jac_node=node,
2019
+ )
2020
+ node.gen.es_ast = func_decl
2021
+
2022
+ # Type and other nodes
2023
+ # ====================
2024
+
2025
+ def exit_token(self, node: uni.Token) -> None:
2026
+ """Process token."""
2027
+ # Tokens are generally not directly converted
2028
+ pass
2029
+
2030
+ def exit_semi(self, node: uni.Semi) -> None:
2031
+ """Process semicolon."""
2032
+ # Semicolons are handled automatically
2033
+ pass
2034
+
2035
+ def _literal_value(self, expr: uni.UniNode | None) -> object | None:
2036
+ """Extract literal value from an expression."""
2037
+ if expr is None:
2038
+ return None
2039
+ literal_attr = "lit_value"
2040
+ if hasattr(expr, literal_attr):
2041
+ return getattr(expr, literal_attr)
2042
+ if isinstance(expr, uni.MultiString):
2043
+ parts: list[str] = []
2044
+ for segment in expr.strings:
2045
+ if hasattr(segment, literal_attr):
2046
+ parts.append(getattr(segment, literal_attr))
2047
+ else:
2048
+ return None
2049
+ return "".join(parts)
2050
+ if isinstance(expr, uni.ListVal):
2051
+ values = [self._literal_value(item) for item in expr.values]
2052
+ if all(val is not None for val in values):
2053
+ return values
2054
+ if isinstance(expr, uni.TupleVal):
2055
+ values = [self._literal_value(item) for item in expr.values]
2056
+ if all(val is not None for val in values):
2057
+ return tuple(values)
2058
+ if isinstance(expr, uni.DictVal):
2059
+ items: dict[str, Any] = {}
2060
+ for pair in expr.kv_pairs:
2061
+ if isinstance(pair, uni.KVPair) and pair.key:
2062
+ key_val = self._literal_value(pair.key)
2063
+ val_val = self._literal_value(pair.value)
2064
+ if isinstance(key_val, str) and val_val is not None:
2065
+ items[key_val] = val_val
2066
+ if items:
2067
+ return items
2068
+ return None