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.
- jaclang/cli/cli.py +147 -25
- jaclang/cli/cmdreg.py +144 -8
- jaclang/compiler/__init__.py +6 -1
- jaclang/compiler/codeinfo.py +16 -1
- jaclang/compiler/constant.py +33 -13
- jaclang/compiler/jac.lark +130 -31
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +567 -176
- 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/pyast_gen_pass.py +324 -234
- jaclang/compiler/passes/main/pyast_load_pass.py +46 -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 -2
- 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_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 +115 -0
- jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
- 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 +22 -25
- 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 +169 -50
- jaclang/compiler/type_system/type_utils.py +1 -1
- jaclang/compiler/type_system/types.py +6 -0
- jaclang/compiler/unitree.py +430 -84
- 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 +1 -1
- jaclang/runtimelib/client_bundle.py +169 -0
- jaclang/runtimelib/client_runtime.jac +586 -0
- jaclang/runtimelib/constructs.py +2 -0
- jaclang/runtimelib/machine.py +259 -100
- 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 -2
- 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/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 +1 -1
- jaclang/tests/test_language.py +10 -39
- jaclang/tests/test_reference.py +17 -2
- jaclang/utils/NonGPT.py +375 -0
- jaclang/utils/helpers.py +44 -16
- jaclang/utils/lang_tools.py +31 -4
- jaclang/utils/tests/test_lang_tools.py +1 -1
- jaclang/utils/treeprinter.py +8 -3
- {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
- {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/RECORD +96 -66
- 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/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.9.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
- {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
|
@@ -16,6 +16,8 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import ast as py_ast
|
|
18
18
|
import os
|
|
19
|
+
import re
|
|
20
|
+
from threading import Event
|
|
19
21
|
from typing import Optional, Sequence, TYPE_CHECKING, TypeAlias, TypeVar, cast
|
|
20
22
|
|
|
21
23
|
import jaclang.compiler.unitree as uni
|
|
@@ -32,11 +34,16 @@ T = TypeVar("T", bound=uni.UniNode)
|
|
|
32
34
|
class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
33
35
|
"""Jac Parser."""
|
|
34
36
|
|
|
35
|
-
def __init__(
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
ir_in: uni.PythonModuleAst,
|
|
40
|
+
prog: JacProgram,
|
|
41
|
+
cancel_token: Event | None = None,
|
|
42
|
+
) -> None:
|
|
36
43
|
"""Initialize parser."""
|
|
37
44
|
self.mod_path = ir_in.loc.mod_path
|
|
38
45
|
self.orig_src = ir_in.loc.orig_src
|
|
39
|
-
Transform.__init__(self, ir_in=ir_in, prog=prog)
|
|
46
|
+
Transform.__init__(self, ir_in=ir_in, prog=prog, cancel_token=cancel_token)
|
|
40
47
|
|
|
41
48
|
def nu(self, node: T) -> T:
|
|
42
49
|
"""Update node."""
|
|
@@ -45,6 +52,8 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
45
52
|
|
|
46
53
|
def convert(self, node: py_ast.AST) -> uni.UniNode:
|
|
47
54
|
"""Get python node type."""
|
|
55
|
+
if self.is_canceled():
|
|
56
|
+
raise StopIteration
|
|
48
57
|
if hasattr(self, f"proc_{pascal_to_snake(type(node).__name__)}"):
|
|
49
58
|
ret = getattr(self, f"proc_{pascal_to_snake(type(node).__name__)}")(node)
|
|
50
59
|
else:
|
|
@@ -1160,7 +1169,7 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
1160
1169
|
else:
|
|
1161
1170
|
raise self.ice()
|
|
1162
1171
|
|
|
1163
|
-
def proc_formatted_value(self, node: py_ast.FormattedValue) -> uni.
|
|
1172
|
+
def proc_formatted_value(self, node: py_ast.FormattedValue) -> uni.FormattedValue:
|
|
1164
1173
|
"""Process python node.
|
|
1165
1174
|
|
|
1166
1175
|
class FormattedValue(expr):
|
|
@@ -1171,10 +1180,15 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
1171
1180
|
format_spec: expr | None
|
|
1172
1181
|
"""
|
|
1173
1182
|
value = self.convert(node.value)
|
|
1183
|
+
if node.format_spec:
|
|
1184
|
+
fmt_spec = cast(uni.Expr, self.convert(node.format_spec))
|
|
1185
|
+
else:
|
|
1186
|
+
fmt_spec = None
|
|
1174
1187
|
if isinstance(value, uni.Expr):
|
|
1175
|
-
ret = uni.
|
|
1176
|
-
|
|
1177
|
-
|
|
1188
|
+
ret = uni.FormattedValue(
|
|
1189
|
+
format_part=value,
|
|
1190
|
+
conversion=node.conversion,
|
|
1191
|
+
format_spec=fmt_spec,
|
|
1178
1192
|
kid=[value],
|
|
1179
1193
|
)
|
|
1180
1194
|
else:
|
|
@@ -1369,13 +1383,34 @@ class PyastBuildPass(Transform[uni.PythonModuleAst, uni.Module]):
|
|
|
1369
1383
|
__match_args__ = ("values",)
|
|
1370
1384
|
values: list[expr]
|
|
1371
1385
|
"""
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1386
|
+
valid: list[uni.Token | uni.FormattedValue] = []
|
|
1387
|
+
for i in node.values:
|
|
1388
|
+
if isinstance(i, py_ast.Constant) and isinstance(i.value, str):
|
|
1389
|
+
valid.append(self.operator(Tok.STRING, i.value))
|
|
1390
|
+
elif isinstance(i, py_ast.FormattedValue):
|
|
1391
|
+
converted = self.convert(i)
|
|
1392
|
+
if isinstance(converted, uni.FormattedValue):
|
|
1393
|
+
valid.append(converted)
|
|
1394
|
+
else:
|
|
1395
|
+
raise self.ice("Invalid node in joined str")
|
|
1396
|
+
ast_seg = py_ast.get_source_segment(self.orig_src.code, node)
|
|
1397
|
+
if ast_seg is None:
|
|
1398
|
+
ast_seg = 'f""'
|
|
1399
|
+
match = re.match(r"(?i)(fr|rf|f)('{3}|\"{3}|'|\")", ast_seg)
|
|
1400
|
+
if match:
|
|
1401
|
+
prefix, quote = match.groups()
|
|
1402
|
+
start = match.group(0)
|
|
1403
|
+
end = quote * (3 if len(quote) == 3 else 1)
|
|
1404
|
+
else:
|
|
1405
|
+
start = "f'"
|
|
1406
|
+
end = "'"
|
|
1407
|
+
tok_start = self.operator(Tok.STRING, start)
|
|
1408
|
+
tok_end = self.operator(Tok.STRING, end)
|
|
1376
1409
|
fstr = uni.FString(
|
|
1410
|
+
start=tok_start,
|
|
1377
1411
|
parts=valid,
|
|
1378
|
-
|
|
1412
|
+
end=tok_end,
|
|
1413
|
+
kid=[tok_start, *valid, tok_end] if valid else [uni.EmptyToken()],
|
|
1379
1414
|
)
|
|
1380
1415
|
return uni.MultiString(strings=[fstr], kid=[fstr])
|
|
1381
1416
|
|
|
@@ -27,6 +27,8 @@ class PyJacAstLinkPass(UniPass):
|
|
|
27
27
|
self, jac_node: uni.UniNode, py_nodes: list[ast3.AST]
|
|
28
28
|
) -> None:
|
|
29
29
|
"""Link jac name ast to py ast nodes."""
|
|
30
|
+
if isinstance(jac_node, uni.ClientFacingNode) and jac_node.is_client_decl:
|
|
31
|
+
return
|
|
30
32
|
jac_node.gen.py_ast = py_nodes
|
|
31
33
|
for i in py_nodes:
|
|
32
34
|
if isinstance(i.jac_link, list): # type: ignore
|
|
@@ -19,6 +19,7 @@ type checking, and semantic analysis throughout the compilation process.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import jaclang.compiler.unitree as uni
|
|
22
|
+
from jaclang.compiler.constant import SymbolAccess
|
|
22
23
|
from jaclang.compiler.passes import UniPass
|
|
23
24
|
from jaclang.compiler.unitree import UniScopeNode
|
|
24
25
|
|
|
@@ -75,7 +76,7 @@ class SymTabBuildPass(UniPass):
|
|
|
75
76
|
def exit_module_path(self, node: uni.ModulePath) -> None:
|
|
76
77
|
if node.alias:
|
|
77
78
|
node.alias.sym_tab.def_insert(node.alias, single_decl="import")
|
|
78
|
-
elif node.path:
|
|
79
|
+
elif node.path and not node.is_import_from:
|
|
79
80
|
if node.parent_of_type(uni.Import) and not (
|
|
80
81
|
node.parent_of_type(uni.Import).from_loc
|
|
81
82
|
and node.parent_of_type(uni.Import).is_jac
|
|
@@ -84,9 +85,25 @@ class SymTabBuildPass(UniPass):
|
|
|
84
85
|
else:
|
|
85
86
|
pass # Need to support pythonic import symbols with dots in it
|
|
86
87
|
|
|
88
|
+
# There will be symbols for
|
|
89
|
+
# import from math {sqrt} <- math will have a symbol but no symtab entry
|
|
90
|
+
# import math as m <- m will have a symbol and symtab entry
|
|
91
|
+
if node.path and (node.is_import_from or (node.alias)):
|
|
92
|
+
for n in node.path:
|
|
93
|
+
n.sym = n.create_symbol(
|
|
94
|
+
access=SymbolAccess.PUBLIC,
|
|
95
|
+
imported=True,
|
|
96
|
+
)
|
|
97
|
+
|
|
87
98
|
def exit_module_item(self, node: uni.ModuleItem) -> None:
|
|
88
99
|
sym_node = node.alias or node.name
|
|
89
100
|
sym_node.sym_tab.def_insert(sym_node, single_decl="import")
|
|
101
|
+
if node.alias:
|
|
102
|
+
# create symbol for module item
|
|
103
|
+
node.name.sym = node.name.create_symbol(
|
|
104
|
+
access=SymbolAccess.PUBLIC,
|
|
105
|
+
imported=True,
|
|
106
|
+
)
|
|
90
107
|
|
|
91
108
|
def enter_archetype(self, node: uni.Archetype) -> None:
|
|
92
109
|
self.push_scope_and_link(node)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
glob RAD = 5.0;
|
|
2
|
+
|
|
3
|
+
obj Circle1{
|
|
4
|
+
has radius:float ,age:int;
|
|
5
|
+
def init(color:str){
|
|
6
|
+
self.color = color;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
with entry {
|
|
10
|
+
c1 = Circle1(RAD);
|
|
11
|
+
}
|
|
12
|
+
# ---------------------------------------------------------------
|
|
13
|
+
glob length = 5.0;
|
|
14
|
+
|
|
15
|
+
obj Square{
|
|
16
|
+
has side_length:float ,age:int;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
with entry {
|
|
20
|
+
c2 = Square(length);
|
|
21
|
+
}
|
|
22
|
+
# ---------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
glob name = "John";
|
|
25
|
+
|
|
26
|
+
obj Person{
|
|
27
|
+
has name:str ,age:int=90;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
with entry {
|
|
31
|
+
c = Person(name=name, age=25);
|
|
32
|
+
c = Person();
|
|
33
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
"""Tests for typechecker pass (the pyright implementation)."""
|
|
3
3
|
|
|
4
|
-
from tempfile import NamedTemporaryFile
|
|
5
|
-
|
|
6
4
|
from jaclang.utils.test import TestCase
|
|
7
5
|
from jaclang.compiler.passes.main import TypeCheckPass
|
|
8
6
|
from jaclang.compiler.program import JacProgram
|
|
@@ -270,6 +268,37 @@ class TypeCheckerPassTests(TestCase):
|
|
|
270
268
|
for i, expected in enumerate(expected_errors):
|
|
271
269
|
self._assert_error_pretty_found(expected, program.errors_had[i].pretty_print())
|
|
272
270
|
|
|
271
|
+
def test_class_construct(self) -> None:
|
|
272
|
+
program = JacProgram()
|
|
273
|
+
path = self.fixture_abs_path("checker_class_construct.jac")
|
|
274
|
+
mod = program.compile(path)
|
|
275
|
+
TypeCheckPass(ir_in=mod, prog=program)
|
|
276
|
+
self.assertEqual(len(program.errors_had), 3)
|
|
277
|
+
|
|
278
|
+
expected_errors = [
|
|
279
|
+
"""
|
|
280
|
+
Cannot assign <class float> to parameter 'color' of type <class str>
|
|
281
|
+
with entry {
|
|
282
|
+
c1 = Circle1(RAD);
|
|
283
|
+
^^^
|
|
284
|
+
""",
|
|
285
|
+
"""
|
|
286
|
+
Not all required parameters were provided in the function call: 'age'
|
|
287
|
+
with entry {
|
|
288
|
+
c2 = Square(length);
|
|
289
|
+
^^^^^^^^^^^^^^
|
|
290
|
+
""",
|
|
291
|
+
"""
|
|
292
|
+
Not all required parameters were provided in the function call: 'name'
|
|
293
|
+
c = Person(name=name, age=25);
|
|
294
|
+
c = Person();
|
|
295
|
+
^^^^^^^^
|
|
296
|
+
""",
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
for i, expected in enumerate(expected_errors):
|
|
300
|
+
self._assert_error_pretty_found(expected, program.errors_had[i].pretty_print())
|
|
301
|
+
|
|
273
302
|
def test_self_type_inference(self) -> None:
|
|
274
303
|
path = self.fixture_abs_path("checker_self_type.jac")
|
|
275
304
|
program = JacProgram()
|
|
@@ -21,3 +21,15 @@ class DefUsePassTests(TestCase):
|
|
|
21
21
|
self.assertEqual(len(uses[1]), 1)
|
|
22
22
|
self.assertIn("output", [uses[0][0].sym_name, uses[1][0].sym_name])
|
|
23
23
|
self.assertIn("message", [uses[0][0].sym_name, uses[1][0].sym_name])
|
|
24
|
+
|
|
25
|
+
def test_def_use_modpath(self) -> None:
|
|
26
|
+
"""Basic test for pass."""
|
|
27
|
+
state = JacProgram().compile(
|
|
28
|
+
file_path=self.fixture_abs_path("defuse_modpath.jac")
|
|
29
|
+
)
|
|
30
|
+
all_symbols = list(
|
|
31
|
+
state.sym_tab.names_in_scope.values()
|
|
32
|
+
)
|
|
33
|
+
self.assertEqual(len(all_symbols), 2)
|
|
34
|
+
self.assertEqual(all_symbols[0].sym_name, "square_root")
|
|
35
|
+
self.assertEqual(all_symbols[1].sym_name, "ThreadPoolExecutor")
|
|
@@ -30,29 +30,31 @@ class ImportPassPassTests(TestCase):
|
|
|
30
30
|
(prog := JacProgram()).compile(self.fixture_abs_path("autoimpl.jac"))
|
|
31
31
|
num_modules = len(list(prog.mod.hub.values())[0].impl_mod)
|
|
32
32
|
mod_names = [i.name for i in list(prog.mod.hub.values())[0].impl_mod]
|
|
33
|
-
self.assertEqual(num_modules,
|
|
33
|
+
self.assertEqual(num_modules, 5)
|
|
34
34
|
self.assertIn("getme.impl", mod_names)
|
|
35
35
|
self.assertIn("autoimpl.impl", mod_names)
|
|
36
36
|
self.assertIn("autoimpl.something.else.impl", mod_names)
|
|
37
|
+
self.assertIn("autoimpl.cl", mod_names)
|
|
37
38
|
|
|
38
39
|
def test_import_include_auto_impl(self) -> None:
|
|
39
40
|
"""Basic test for pass."""
|
|
40
41
|
(prog := JacProgram()).build(self.fixture_abs_path("incautoimpl.jac"))
|
|
41
42
|
num_modules = len(list(prog.mod.hub.values())[1].impl_mod) + 1
|
|
42
43
|
mod_names = [i.name for i in list(prog.mod.hub.values())[1].impl_mod]
|
|
43
|
-
self.assertEqual(num_modules,
|
|
44
|
+
self.assertEqual(num_modules, 6)
|
|
44
45
|
self.assertEqual("incautoimpl", list(prog.mod.hub.values())[0].name)
|
|
45
46
|
self.assertEqual("autoimpl", list(prog.mod.hub.values())[1].name)
|
|
46
47
|
self.assertIn("getme.impl", mod_names)
|
|
47
48
|
self.assertIn("autoimpl.impl", mod_names)
|
|
48
49
|
self.assertIn("autoimpl.something.else.impl", mod_names)
|
|
50
|
+
self.assertIn("autoimpl.cl", mod_names)
|
|
49
51
|
|
|
50
52
|
def test_annexalbe_by_discovery(self) -> None:
|
|
51
53
|
"""Basic test for pass."""
|
|
52
54
|
(prog := JacProgram()).build(self.fixture_abs_path("incautoimpl.jac"))
|
|
53
55
|
count = 0
|
|
54
56
|
all_mods = prog.mod.hub.values()
|
|
55
|
-
self.assertEqual(len(all_mods),
|
|
57
|
+
self.assertEqual(len(all_mods), 7)
|
|
56
58
|
for main_mod in all_mods:
|
|
57
59
|
for i in main_mod.impl_mod:
|
|
58
60
|
if i.name not in ["autoimpl", "incautoimpl"]:
|
|
@@ -60,7 +62,24 @@ class ImportPassPassTests(TestCase):
|
|
|
60
62
|
self.assertEqual(
|
|
61
63
|
i.annexable_by, self.fixture_abs_path("autoimpl.jac")
|
|
62
64
|
)
|
|
63
|
-
self.assertEqual(count,
|
|
65
|
+
self.assertEqual(count, 5)
|
|
66
|
+
|
|
67
|
+
def test_cl_annex_marked_client(self) -> None:
|
|
68
|
+
"""Ensure .cl.jac annex files are autoloaded and marked client."""
|
|
69
|
+
|
|
70
|
+
(prog := JacProgram()).compile(self.fixture_abs_path("autoimpl.jac"))
|
|
71
|
+
main_mod = list(prog.mod.hub.values())[0]
|
|
72
|
+
cl_mod = next(
|
|
73
|
+
(mod for mod in main_mod.impl_mod if mod.name.endswith(".cl")), None
|
|
74
|
+
)
|
|
75
|
+
self.assertIsNotNone(cl_mod, "Expected .cl annex module to be loaded")
|
|
76
|
+
abilities = cl_mod.get_all_sub_nodes(uni.Ability)
|
|
77
|
+
self.assertTrue(abilities, "Expected abilities in .cl annex module")
|
|
78
|
+
for ability in abilities:
|
|
79
|
+
self.assertTrue(
|
|
80
|
+
ability.is_client_decl,
|
|
81
|
+
"All client annex abilities should be marked as client declarations",
|
|
82
|
+
)
|
|
64
83
|
|
|
65
84
|
@unittest.skip("TODO: Fix when we have the type checker")
|
|
66
85
|
def test_py_raise_map(self) -> None:
|
|
@@ -153,6 +153,31 @@ class PyastGenPassTests(TestCaseMicroSuite, AstSyncTestMixin):
|
|
|
153
153
|
|
|
154
154
|
self.assertFalse(out.errors_had)
|
|
155
155
|
|
|
156
|
+
def test_iife_fixture_executes(self) -> None:
|
|
157
|
+
"""Ensure IIFE and block lambdas lower to executable Python."""
|
|
158
|
+
fixture_path = self.lang_fixture_abs_path("iife_functions.jac")
|
|
159
|
+
code_gen = (prog := JacProgram()).compile(fixture_path)
|
|
160
|
+
self.assertFalse(prog.errors_had)
|
|
161
|
+
if code_gen.gen.py_ast and isinstance(code_gen.gen.py_ast[0], ast3.Module):
|
|
162
|
+
module_ast = code_gen.gen.py_ast[0]
|
|
163
|
+
compiled = compile(module_ast, filename="<ast>", mode="exec")
|
|
164
|
+
captured = io.StringIO()
|
|
165
|
+
original_stdout = sys.stdout
|
|
166
|
+
try:
|
|
167
|
+
sys.stdout = captured
|
|
168
|
+
module = types.ModuleType("__main__")
|
|
169
|
+
module.__dict__["__file__"] = code_gen.loc.mod_path
|
|
170
|
+
exec(compiled, module.__dict__)
|
|
171
|
+
finally:
|
|
172
|
+
sys.stdout = original_stdout
|
|
173
|
+
output = captured.getvalue()
|
|
174
|
+
self.assertIn("Test 1 - Basic IIFE: 42", output)
|
|
175
|
+
self.assertIn(
|
|
176
|
+
"Test 6 - IIFE returning function, adder(5): 15",
|
|
177
|
+
output,
|
|
178
|
+
)
|
|
179
|
+
self.assertIn("All IIFE tests completed!", output)
|
|
180
|
+
|
|
156
181
|
def parent_scrub(self, node: uni.UniNode) -> bool:
|
|
157
182
|
"""Validate every node has parent."""
|
|
158
183
|
success = True
|
|
@@ -69,10 +69,17 @@ class TypeCheckPass(UniPass):
|
|
|
69
69
|
|
|
70
70
|
def exit_import(self, node: uni.Import) -> None:
|
|
71
71
|
"""Exit an import node."""
|
|
72
|
+
# import from math {sqrt, sin as s}
|
|
72
73
|
if node.from_loc:
|
|
74
|
+
self.evaluator.get_type_of_module(node.from_loc)
|
|
73
75
|
for item in node.items:
|
|
74
76
|
if isinstance(item, uni.ModuleItem):
|
|
75
77
|
self.evaluator.get_type_of_module_item(item)
|
|
78
|
+
else:
|
|
79
|
+
# import math as m, os, sys;
|
|
80
|
+
for item in node.items:
|
|
81
|
+
if isinstance(item, uni.ModulePath):
|
|
82
|
+
self.evaluator.get_type_of_module(item)
|
|
76
83
|
|
|
77
84
|
def exit_assignment(self, node: uni.Assignment) -> None:
|
|
78
85
|
"""Pyright: Checker.visitAssignment(node: AssignmentNode): boolean."""
|
|
@@ -892,6 +892,13 @@ class DocIRGenPass(UniPass):
|
|
|
892
892
|
parts.append(i.gen.doc_ir)
|
|
893
893
|
node.gen.doc_ir = self.group(self.concat(parts))
|
|
894
894
|
|
|
895
|
+
def exit_formatted_value(self, node: uni.FormattedValue) -> None:
|
|
896
|
+
"""Generate DocIR for formatted value expressions."""
|
|
897
|
+
parts: list[doc.DocType] = []
|
|
898
|
+
for i in node.kid:
|
|
899
|
+
parts.append(i.gen.doc_ir)
|
|
900
|
+
node.gen.doc_ir = self.group(self.concat(parts))
|
|
901
|
+
|
|
895
902
|
def exit_if_else_expr(self, node: uni.IfElseExpr) -> None:
|
|
896
903
|
"""Generate DocIR for conditional expressions."""
|
|
897
904
|
parts: list[doc.DocType] = []
|
|
@@ -1747,3 +1754,111 @@ class DocIRGenPass(UniPass):
|
|
|
1747
1754
|
node.gen.doc_ir = self.group(
|
|
1748
1755
|
self.concat([self.text(node.value), self.hard_line()])
|
|
1749
1756
|
)
|
|
1757
|
+
|
|
1758
|
+
def exit_jsx_element(self, node: uni.JsxElement) -> None:
|
|
1759
|
+
"""Generate DocIR for JSX elements - kid-centric beautiful formatting!"""
|
|
1760
|
+
parts: list[doc.DocType] = []
|
|
1761
|
+
prev = None
|
|
1762
|
+
|
|
1763
|
+
# Check if we have any JSX element children
|
|
1764
|
+
# Use node.children instead of node.kid to avoid counting opening/closing tags
|
|
1765
|
+
has_jsx_elem_children = any(
|
|
1766
|
+
isinstance(k, uni.JsxElement) for k in node.children
|
|
1767
|
+
)
|
|
1768
|
+
|
|
1769
|
+
# Only break/indent if we have JSX element children
|
|
1770
|
+
# (simple text/expression children stay inline)
|
|
1771
|
+
should_format_children = has_jsx_elem_children
|
|
1772
|
+
|
|
1773
|
+
for i in node.kid:
|
|
1774
|
+
# Add line break between attributes (allows them to wrap nicely)
|
|
1775
|
+
if (
|
|
1776
|
+
prev
|
|
1777
|
+
and isinstance(prev, (uni.JsxElementName, uni.JsxAttribute))
|
|
1778
|
+
and isinstance(i, uni.JsxAttribute)
|
|
1779
|
+
):
|
|
1780
|
+
parts.append(self.line())
|
|
1781
|
+
# Add hard line between JSX element children, or before first child
|
|
1782
|
+
elif (
|
|
1783
|
+
prev
|
|
1784
|
+
and (
|
|
1785
|
+
(
|
|
1786
|
+
isinstance(prev, (uni.JsxChild, uni.JsxElement))
|
|
1787
|
+
and isinstance(i, (uni.JsxChild, uni.JsxElement))
|
|
1788
|
+
)
|
|
1789
|
+
or (
|
|
1790
|
+
isinstance(prev, (uni.JsxElementName, uni.JsxAttribute))
|
|
1791
|
+
and isinstance(i, (uni.JsxChild, uni.JsxElement))
|
|
1792
|
+
)
|
|
1793
|
+
)
|
|
1794
|
+
and should_format_children
|
|
1795
|
+
):
|
|
1796
|
+
parts.append(self.hard_line())
|
|
1797
|
+
|
|
1798
|
+
# Indent JSX element children, but not text/expression children
|
|
1799
|
+
if isinstance(i, uni.JsxElement) and should_format_children:
|
|
1800
|
+
parts.append(self.indent(i.gen.doc_ir))
|
|
1801
|
+
else:
|
|
1802
|
+
parts.append(i.gen.doc_ir)
|
|
1803
|
+
|
|
1804
|
+
prev = i
|
|
1805
|
+
|
|
1806
|
+
node.gen.doc_ir = self.group(self.concat(parts))
|
|
1807
|
+
|
|
1808
|
+
def exit_jsx_element_name(self, node: uni.JsxElementName) -> None:
|
|
1809
|
+
"""Generate DocIR for JSX element names."""
|
|
1810
|
+
parts: list[doc.DocType] = []
|
|
1811
|
+
for i in node.kid:
|
|
1812
|
+
parts.append(i.gen.doc_ir)
|
|
1813
|
+
node.gen.doc_ir = self.concat(parts)
|
|
1814
|
+
|
|
1815
|
+
def exit_jsx_spread_attribute(self, node: uni.JsxSpreadAttribute) -> None:
|
|
1816
|
+
"""Generate DocIR for JSX spread attributes."""
|
|
1817
|
+
parts: list[doc.DocType] = []
|
|
1818
|
+
for i in node.kid:
|
|
1819
|
+
parts.append(i.gen.doc_ir)
|
|
1820
|
+
node.gen.doc_ir = self.concat(parts)
|
|
1821
|
+
|
|
1822
|
+
def exit_jsx_normal_attribute(self, node: uni.JsxNormalAttribute) -> None:
|
|
1823
|
+
"""Generate DocIR for JSX normal attributes."""
|
|
1824
|
+
# Normalize to ensure LBRACE/RBRACE tokens are added for expression values
|
|
1825
|
+
node.normalize()
|
|
1826
|
+
parts: list[doc.DocType] = []
|
|
1827
|
+
for i in node.kid:
|
|
1828
|
+
# Tokens created by normalize() have empty doc_ir, so regenerate it
|
|
1829
|
+
if (
|
|
1830
|
+
isinstance(i, uni.Token)
|
|
1831
|
+
and isinstance(i.gen.doc_ir, doc.Text)
|
|
1832
|
+
and not i.gen.doc_ir.text
|
|
1833
|
+
):
|
|
1834
|
+
i.gen.doc_ir = self.text(i.value)
|
|
1835
|
+
elif not isinstance(
|
|
1836
|
+
i.gen.doc_ir,
|
|
1837
|
+
(
|
|
1838
|
+
doc.Text,
|
|
1839
|
+
doc.Concat,
|
|
1840
|
+
doc.Group,
|
|
1841
|
+
doc.Indent,
|
|
1842
|
+
doc.Line,
|
|
1843
|
+
doc.Align,
|
|
1844
|
+
doc.IfBreak,
|
|
1845
|
+
),
|
|
1846
|
+
):
|
|
1847
|
+
# For nodes with invalid doc_ir, generate it by visiting
|
|
1848
|
+
self.enter_exit(i)
|
|
1849
|
+
parts.append(i.gen.doc_ir)
|
|
1850
|
+
node.gen.doc_ir = self.concat(parts)
|
|
1851
|
+
|
|
1852
|
+
def exit_jsx_text(self, node: uni.JsxText) -> None:
|
|
1853
|
+
"""Generate DocIR for JSX text."""
|
|
1854
|
+
parts: list[doc.DocType] = []
|
|
1855
|
+
for i in node.kid:
|
|
1856
|
+
parts.append(i.gen.doc_ir)
|
|
1857
|
+
node.gen.doc_ir = self.concat(parts)
|
|
1858
|
+
|
|
1859
|
+
def exit_jsx_expression(self, node: uni.JsxExpression) -> None:
|
|
1860
|
+
"""Generate DocIR for JSX expressions."""
|
|
1861
|
+
parts: list[doc.DocType] = []
|
|
1862
|
+
for i in node.kid:
|
|
1863
|
+
parts.append(i.gen.doc_ir)
|
|
1864
|
+
node.gen.doc_ir = self.concat(parts)
|
|
@@ -41,19 +41,9 @@ class FuseCommentsPass(UniPass):
|
|
|
41
41
|
except StopIteration:
|
|
42
42
|
next_comment = None
|
|
43
43
|
|
|
44
|
-
# Handle possible leading comments
|
|
45
|
-
if next_comment and (not next_code or _is_before(next_comment, next_code)):
|
|
46
|
-
self.ir_out.src_terminals.insert(0, next_comment)
|
|
47
|
-
|
|
48
44
|
# Merge streams in order
|
|
49
45
|
while next_comment or next_code:
|
|
50
46
|
if next_comment and (not next_code or _is_before(next_comment, next_code)):
|
|
51
|
-
# Add comment token
|
|
52
|
-
if merged and (last_token := merged[-1]):
|
|
53
|
-
self.ir_out.src_terminals.insert(
|
|
54
|
-
self.ir_out.src_terminals.index(last_token) + 1,
|
|
55
|
-
next_comment,
|
|
56
|
-
)
|
|
57
47
|
merged.append(next_comment)
|
|
58
48
|
try:
|
|
59
49
|
next_comment = next(comments)
|
|
@@ -66,6 +56,7 @@ class FuseCommentsPass(UniPass):
|
|
|
66
56
|
next_code = next(code_tokens)
|
|
67
57
|
except StopIteration:
|
|
68
58
|
next_code = None
|
|
59
|
+
self.ir_out.src_terminals[:] = merged
|
|
69
60
|
|
|
70
61
|
return merged
|
|
71
62
|
|
|
@@ -133,6 +133,8 @@ class JacFormatPassTests(TestCaseMicroSuite):
|
|
|
133
133
|
self.assertEqual(tokens[i + 1], "{")
|
|
134
134
|
self.assertEqual(num_test, 3)
|
|
135
135
|
return
|
|
136
|
+
before = ""
|
|
137
|
+
after = ""
|
|
136
138
|
try:
|
|
137
139
|
before = ast3.dump(code_gen_pure.gen.py_ast[0], indent=2)
|
|
138
140
|
after = ast3.dump(code_gen_jac.gen.py_ast[0], indent=2)
|
|
@@ -151,7 +153,8 @@ class JacFormatPassTests(TestCaseMicroSuite):
|
|
|
151
153
|
print("\n+++++++++++++++++++++++++++++++++++++++\n")
|
|
152
154
|
print(add_line_numbers(code_gen_format))
|
|
153
155
|
print("\n+++++++++++++++++++++++++++++++++++++++\n")
|
|
154
|
-
|
|
156
|
+
if before and after:
|
|
157
|
+
print("\n".join(unified_diff(before.splitlines(), after.splitlines())))
|
|
155
158
|
raise e
|
|
156
159
|
|
|
157
160
|
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from threading import Event
|
|
7
8
|
from typing import Generic, Optional, TYPE_CHECKING, Type, TypeVar
|
|
8
9
|
|
|
9
10
|
from jaclang.compiler.codeinfo import CodeLocInfo
|
|
@@ -70,7 +71,9 @@ class Alert:
|
|
|
70
71
|
class Transform(ABC, Generic[T, R]):
|
|
71
72
|
"""Abstract class for IR passes."""
|
|
72
73
|
|
|
73
|
-
def __init__(
|
|
74
|
+
def __init__(
|
|
75
|
+
self, ir_in: T, prog: JacProgram, cancel_token: Event | None = None
|
|
76
|
+
) -> None:
|
|
74
77
|
"""Initialize pass."""
|
|
75
78
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
76
79
|
self.errors_had: list[Alert] = []
|
|
@@ -79,6 +82,7 @@ class Transform(ABC, Generic[T, R]):
|
|
|
79
82
|
self.prog = prog
|
|
80
83
|
self.time_taken = 0.0
|
|
81
84
|
self.ir_in: T = ir_in
|
|
85
|
+
self.cancel_token = cancel_token
|
|
82
86
|
self.pre_transform()
|
|
83
87
|
self.ir_out: R = self.timed_transform(ir_in=ir_in)
|
|
84
88
|
self.post_transform()
|
|
@@ -136,6 +140,10 @@ class Transform(ABC, Generic[T, R]):
|
|
|
136
140
|
"""Log info."""
|
|
137
141
|
self.logger.info(msg)
|
|
138
142
|
|
|
143
|
+
def is_canceled(self) -> bool:
|
|
144
|
+
"""Check if the pass has been canceled."""
|
|
145
|
+
return self.cancel_token is not None and self.cancel_token.is_set()
|
|
146
|
+
|
|
139
147
|
def ice(self, msg: str = "Something went horribly wrong!") -> RuntimeError:
|
|
140
148
|
"""Pass Error."""
|
|
141
149
|
self.log_error(f"ICE: Pass {self.__class__.__name__} - {msg}")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from threading import Event
|
|
5
6
|
from typing import Optional, TYPE_CHECKING, Type, TypeVar
|
|
6
7
|
|
|
7
8
|
import jaclang.compiler.unitree as uni
|
|
@@ -21,11 +22,12 @@ class UniPass(Transform[uni.Module, uni.Module]):
|
|
|
21
22
|
self,
|
|
22
23
|
ir_in: uni.Module,
|
|
23
24
|
prog: JacProgram,
|
|
25
|
+
cancel_token: Event | None = None,
|
|
24
26
|
) -> None:
|
|
25
27
|
"""Initialize parser."""
|
|
26
28
|
self.term_signal = False
|
|
27
29
|
self.prune_signal = False
|
|
28
|
-
Transform.__init__(self, ir_in, prog)
|
|
30
|
+
Transform.__init__(self, ir_in, prog, cancel_token=cancel_token)
|
|
29
31
|
|
|
30
32
|
def before_pass(self) -> None:
|
|
31
33
|
"""Run once before pass."""
|
|
@@ -43,10 +45,6 @@ class UniPass(Transform[uni.Module, uni.Module]):
|
|
|
43
45
|
if hasattr(self, f"exit_{pascal_to_snake(type(node).__name__)}"):
|
|
44
46
|
getattr(self, f"exit_{pascal_to_snake(type(node).__name__)}")(node)
|
|
45
47
|
|
|
46
|
-
def terminate(self) -> None:
|
|
47
|
-
"""Terminate traversal."""
|
|
48
|
-
self.term_signal = True
|
|
49
|
-
|
|
50
48
|
def prune(self) -> None:
|
|
51
49
|
"""Prune traversal."""
|
|
52
50
|
self.prune_signal = True
|
|
@@ -120,7 +118,7 @@ class UniPass(Transform[uni.Module, uni.Module]):
|
|
|
120
118
|
|
|
121
119
|
def traverse(self, node: uni.UniNode) -> uni.UniNode:
|
|
122
120
|
"""Traverse tree."""
|
|
123
|
-
if self.
|
|
121
|
+
if self.is_canceled():
|
|
124
122
|
return node
|
|
125
123
|
self.cur_node = node
|
|
126
124
|
self.enter_node(node)
|
|
@@ -131,7 +129,7 @@ class UniPass(Transform[uni.Module, uni.Module]):
|
|
|
131
129
|
else:
|
|
132
130
|
self.prune_signal = False
|
|
133
131
|
self.cur_node = node
|
|
134
|
-
if self.
|
|
132
|
+
if self.is_canceled():
|
|
135
133
|
return node
|
|
136
134
|
self.exit_node(node)
|
|
137
135
|
return node
|