jaclang 0.8.8__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.
- jaclang/cli/cli.py +194 -10
- jaclang/cli/cmdreg.py +144 -8
- jaclang/compiler/__init__.py +6 -1
- jaclang/compiler/codeinfo.py +16 -1
- jaclang/compiler/constant.py +33 -8
- jaclang/compiler/jac.lark +154 -62
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +656 -149
- jaclang/compiler/passes/__init__.py +2 -1
- jaclang/compiler/passes/ast_gen/__init__.py +5 -0
- jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
- jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
- jaclang/compiler/passes/ecmascript/__init__.py +25 -0
- jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
- jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
- jaclang/compiler/passes/ecmascript/estree.py +972 -0
- jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
- jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
- jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
- jaclang/compiler/passes/main/__init__.py +0 -3
- jaclang/compiler/passes/main/annex_pass.py +23 -1
- jaclang/compiler/passes/main/def_use_pass.py +1 -0
- jaclang/compiler/passes/main/pyast_gen_pass.py +413 -255
- jaclang/compiler/passes/main/pyast_load_pass.py +48 -11
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
- jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
- jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
- jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
- jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -3
- jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
- jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
- jaclang/compiler/passes/main/tests/test_predynamo_pass.py +13 -14
- jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
- jaclang/compiler/passes/main/type_checker_pass.py +7 -0
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +219 -20
- jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
- jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
- jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +7 -1
- jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +135 -29
- jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
- jaclang/compiler/passes/transform.py +9 -1
- jaclang/compiler/passes/uni_pass.py +5 -7
- jaclang/compiler/program.py +27 -26
- jaclang/compiler/tests/test_client_codegen.py +113 -0
- jaclang/compiler/tests/test_importer.py +12 -10
- jaclang/compiler/tests/test_parser.py +249 -3
- jaclang/compiler/type_system/type_evaluator.jac +1078 -0
- jaclang/compiler/type_system/type_utils.py +1 -1
- jaclang/compiler/type_system/types.py +6 -0
- jaclang/compiler/unitree.py +438 -82
- jaclang/langserve/engine.jac +224 -288
- jaclang/langserve/sem_manager.jac +12 -8
- jaclang/langserve/server.jac +48 -48
- jaclang/langserve/tests/fixtures/greet.py +17 -0
- jaclang/langserve/tests/fixtures/md_path.jac +22 -0
- jaclang/langserve/tests/fixtures/user.jac +15 -0
- jaclang/langserve/tests/test_server.py +66 -371
- jaclang/lib.py +17 -0
- jaclang/runtimelib/archetype.py +25 -25
- jaclang/runtimelib/client_bundle.py +169 -0
- jaclang/runtimelib/client_runtime.jac +586 -0
- jaclang/runtimelib/constructs.py +4 -2
- jaclang/runtimelib/machine.py +308 -139
- jaclang/runtimelib/meta_importer.py +111 -22
- jaclang/runtimelib/mtp.py +15 -0
- jaclang/runtimelib/server.py +1089 -0
- jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
- jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
- jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
- jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
- jaclang/runtimelib/tests/test_client_bundle.py +55 -0
- jaclang/runtimelib/tests/test_client_render.py +63 -0
- jaclang/runtimelib/tests/test_serve.py +1069 -0
- jaclang/settings.py +0 -3
- jaclang/tests/fixtures/attr_pattern_case.jac +18 -0
- jaclang/tests/fixtures/funccall_genexpr.jac +7 -0
- jaclang/tests/fixtures/funccall_genexpr.py +5 -0
- jaclang/tests/fixtures/iife_functions.jac +142 -0
- jaclang/tests/fixtures/iife_functions_client.jac +143 -0
- jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
- jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
- jaclang/tests/fixtures/needs_import_dup.jac +6 -4
- jaclang/tests/fixtures/py2jac_empty.py +0 -0
- jaclang/tests/fixtures/py_run.py +7 -5
- jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
- jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
- jaclang/tests/test_cli.py +134 -18
- jaclang/tests/test_language.py +120 -32
- jaclang/tests/test_reference.py +20 -3
- jaclang/utils/NonGPT.py +375 -0
- jaclang/utils/helpers.py +64 -20
- jaclang/utils/lang_tools.py +31 -4
- jaclang/utils/tests/test_lang_tools.py +5 -16
- jaclang/utils/treeprinter.py +8 -3
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/RECORD +106 -71
- jaclang/compiler/passes/main/binder_pass.py +0 -594
- jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
- jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
- jaclang/compiler/type_system/type_evaluator.py +0 -844
- jaclang/langserve/tests/session.jac +0 -294
- jaclang/langserve/tests/test_dev_server.py +0 -80
- jaclang/runtimelib/importer.py +0 -351
- jaclang/tests/test_typecheck.py +0 -542
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
- {jaclang-0.8.8.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
|