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
jaclang/compiler/program.py
CHANGED
|
@@ -5,13 +5,14 @@ from __future__ import annotations
|
|
|
5
5
|
import ast as py_ast
|
|
6
6
|
import marshal
|
|
7
7
|
import types
|
|
8
|
+
from threading import Event
|
|
8
9
|
from typing import Optional, TYPE_CHECKING
|
|
9
10
|
|
|
10
11
|
import jaclang.compiler.unitree as uni
|
|
11
12
|
from jaclang.compiler.parser import JacParser
|
|
13
|
+
from jaclang.compiler.passes.ecmascript import EsastGenPass
|
|
12
14
|
from jaclang.compiler.passes.main import (
|
|
13
15
|
Alert,
|
|
14
|
-
BinderPass,
|
|
15
16
|
CFGBuildPass,
|
|
16
17
|
DeclImplMatchPass,
|
|
17
18
|
DefUsePass,
|
|
@@ -34,14 +35,10 @@ from jaclang.compiler.passes.tool import (
|
|
|
34
35
|
)
|
|
35
36
|
from jaclang.runtimelib.utils import read_file_with_encoding
|
|
36
37
|
from jaclang.settings import settings
|
|
37
|
-
from jaclang.utils.log import logging
|
|
38
38
|
|
|
39
39
|
if TYPE_CHECKING:
|
|
40
40
|
from jaclang.compiler.type_system.type_evaluator import TypeEvaluator
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
logger = logging.getLogger(__name__)
|
|
44
|
-
|
|
45
42
|
ir_gen_sched = [
|
|
46
43
|
SymTabBuildPass,
|
|
47
44
|
DeclImplMatchPass,
|
|
@@ -53,6 +50,7 @@ type_check_sched: list = [
|
|
|
53
50
|
TypeCheckPass,
|
|
54
51
|
]
|
|
55
52
|
py_code_gen = [
|
|
53
|
+
EsastGenPass,
|
|
56
54
|
PyastGenPass,
|
|
57
55
|
PyJacAstLinkPass,
|
|
58
56
|
PyBytecodeGenPass,
|
|
@@ -63,7 +61,10 @@ format_sched = [FuseCommentsPass, DocIRGenPass, JacFormatPass]
|
|
|
63
61
|
class JacProgram:
|
|
64
62
|
"""JacProgram to handle the Jac program-related functionalities."""
|
|
65
63
|
|
|
66
|
-
def __init__(
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
main_mod: Optional[uni.ProgramModule] = None,
|
|
67
|
+
) -> None:
|
|
67
68
|
"""Initialize the JacProgram object."""
|
|
68
69
|
self.mod: uni.ProgramModule = main_mod if main_mod else uni.ProgramModule()
|
|
69
70
|
self.py_raise_map: dict[str, str] = {}
|
|
@@ -87,7 +88,9 @@ class JacProgram:
|
|
|
87
88
|
result = self.compile(file_path=full_target)
|
|
88
89
|
return marshal.loads(result.gen.py_bytecode) if result.gen.py_bytecode else None
|
|
89
90
|
|
|
90
|
-
def parse_str(
|
|
91
|
+
def parse_str(
|
|
92
|
+
self, source_str: str, file_path: str, cancel_token: Event | None = None
|
|
93
|
+
) -> uni.Module:
|
|
91
94
|
"""Convert a Jac file to an AST."""
|
|
92
95
|
had_error = False
|
|
93
96
|
if file_path.endswith(".py") or file_path.endswith(".pyi"):
|
|
@@ -98,6 +101,7 @@ class JacProgram:
|
|
|
98
101
|
orig_src=uni.Source(source_str, mod_path=file_path),
|
|
99
102
|
),
|
|
100
103
|
prog=self,
|
|
104
|
+
cancel_token=cancel_token,
|
|
101
105
|
)
|
|
102
106
|
had_error = len(py_ast_ret.errors_had) > 0
|
|
103
107
|
mod = py_ast_ret.ir_out
|
|
@@ -124,25 +128,23 @@ class JacProgram:
|
|
|
124
128
|
# options in it.
|
|
125
129
|
no_cgen: bool = False,
|
|
126
130
|
type_check: bool = False,
|
|
131
|
+
cancel_token: Event | None = None,
|
|
127
132
|
) -> uni.Module:
|
|
128
133
|
"""Convert a Jac file to an AST."""
|
|
129
134
|
keep_str = use_str or read_file_with_encoding(file_path)
|
|
130
|
-
mod_targ = self.parse_str(keep_str, file_path)
|
|
131
|
-
self.run_schedule(mod=mod_targ, passes=ir_gen_sched)
|
|
135
|
+
mod_targ = self.parse_str(keep_str, file_path, cancel_token=cancel_token)
|
|
136
|
+
self.run_schedule(mod=mod_targ, passes=ir_gen_sched, cancel_token=cancel_token)
|
|
132
137
|
if type_check:
|
|
133
|
-
self.run_schedule(
|
|
138
|
+
self.run_schedule(
|
|
139
|
+
mod=mod_targ, passes=type_check_sched, cancel_token=cancel_token
|
|
140
|
+
)
|
|
134
141
|
# If the module has syntax errors, we skip code generation.
|
|
135
142
|
if (not mod_targ.has_syntax_errors) and (not no_cgen):
|
|
136
143
|
if settings.predynamo_pass and PreDynamoPass not in py_code_gen:
|
|
137
144
|
py_code_gen.insert(0, PreDynamoPass)
|
|
138
|
-
self.run_schedule(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def bind(self, file_path: str, use_str: str | None = None) -> uni.Module:
|
|
142
|
-
"""Bind the Jac module."""
|
|
143
|
-
keep_str = use_str or read_file_with_encoding(file_path)
|
|
144
|
-
mod_targ = self.parse_str(keep_str, file_path)
|
|
145
|
-
BinderPass(ir_in=mod_targ, prog=self)
|
|
145
|
+
self.run_schedule(
|
|
146
|
+
mod=mod_targ, passes=py_code_gen, cancel_token=cancel_token
|
|
147
|
+
)
|
|
146
148
|
return mod_targ
|
|
147
149
|
|
|
148
150
|
def build(
|
|
@@ -159,16 +161,11 @@ class JacProgram:
|
|
|
159
161
|
self,
|
|
160
162
|
mod: uni.Module,
|
|
161
163
|
passes: list[type[Transform[uni.Module, uni.Module]]],
|
|
164
|
+
cancel_token: Event | None = None,
|
|
162
165
|
) -> None:
|
|
163
166
|
"""Run the passes on the module."""
|
|
164
|
-
final_pass: Optional[type[Transform[uni.Module, uni.Module]]] = None
|
|
165
167
|
for current_pass in passes:
|
|
166
|
-
|
|
167
|
-
final_pass = current_pass
|
|
168
|
-
break
|
|
169
|
-
current_pass(ir_in=mod, prog=self) # type: ignore
|
|
170
|
-
if final_pass:
|
|
171
|
-
final_pass(mod, prog=self)
|
|
168
|
+
current_pass(ir_in=mod, prog=self, cancel_token=cancel_token) # type: ignore
|
|
172
169
|
|
|
173
170
|
@staticmethod
|
|
174
171
|
def jac_file_formatter(file_path: str) -> str:
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from jaclang.compiler.program import JacProgram
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
FIXTURE_DIR = Path(__file__).resolve().parent.parent / "passes" / "ecmascript" / "tests" / "fixtures"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_js_codegen_generates_js_and_manifest() -> None:
|
|
12
|
+
fixture = FIXTURE_DIR / "client_jsx.jac"
|
|
13
|
+
prog = JacProgram()
|
|
14
|
+
module = prog.compile(str(fixture))
|
|
15
|
+
|
|
16
|
+
assert module.gen.js.strip(), "Expected JavaScript output for client declarations"
|
|
17
|
+
assert "function component" in module.gen.js
|
|
18
|
+
assert "__jacJsx(" in module.gen.js
|
|
19
|
+
|
|
20
|
+
# Client Python code should be omitted in js_only mode
|
|
21
|
+
assert "def component" not in module.gen.py
|
|
22
|
+
|
|
23
|
+
# Metadata should be stored in module.gen.client_manifest
|
|
24
|
+
assert "__jac_client_manifest__" not in module.gen.py
|
|
25
|
+
manifest = module.gen.client_manifest
|
|
26
|
+
assert manifest, "Client manifest should be available in module.gen"
|
|
27
|
+
assert "component" in manifest.exports
|
|
28
|
+
assert "ButtonProps" in manifest.exports
|
|
29
|
+
assert "API_URL" in manifest.globals
|
|
30
|
+
|
|
31
|
+
# Module.gen.client_manifest should have the metadata
|
|
32
|
+
assert "component" in module.gen.client_manifest.exports
|
|
33
|
+
assert "ButtonProps" in module.gen.client_manifest.exports
|
|
34
|
+
assert "API_URL" in module.gen.client_manifest.globals
|
|
35
|
+
assert module.gen.client_manifest.params.get("component", []) == []
|
|
36
|
+
assert "ButtonProps" not in module.gen.client_manifest.params
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_compilation_skips_python_stubs() -> None:
|
|
40
|
+
fixture = FIXTURE_DIR / "client_jsx.jac"
|
|
41
|
+
prog = JacProgram()
|
|
42
|
+
module = prog.compile(str(fixture))
|
|
43
|
+
|
|
44
|
+
assert module.gen.js.strip(), "Expected JavaScript output when emitting both"
|
|
45
|
+
assert "function component" in module.gen.js
|
|
46
|
+
assert "__jacJsx(" in module.gen.js
|
|
47
|
+
|
|
48
|
+
# Client Python definitions are intentionally omitted
|
|
49
|
+
assert "def component" not in module.gen.py
|
|
50
|
+
assert "__jac_client__" not in module.gen.py
|
|
51
|
+
assert "class ButtonProps" not in module.gen.py
|
|
52
|
+
|
|
53
|
+
# Manifest data should be in module.gen.client_manifest
|
|
54
|
+
assert "__jac_client_manifest__" not in module.gen.py
|
|
55
|
+
manifest = module.gen.client_manifest
|
|
56
|
+
assert manifest, "Client manifest should be available in module.gen"
|
|
57
|
+
assert "component" in manifest.exports
|
|
58
|
+
assert "ButtonProps" in manifest.exports
|
|
59
|
+
assert "API_URL" in manifest.globals
|
|
60
|
+
|
|
61
|
+
# Module.gen.client_manifest should have the metadata
|
|
62
|
+
assert "component" in module.gen.client_manifest.exports
|
|
63
|
+
assert "ButtonProps" in module.gen.client_manifest.exports
|
|
64
|
+
assert "API_URL" in module.gen.client_manifest.globals
|
|
65
|
+
assert module.gen.client_manifest.params.get("component", []) == []
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_type_to_typeof_conversion() -> None:
|
|
69
|
+
"""Test that type() calls are converted to typeof in JavaScript."""
|
|
70
|
+
from tempfile import NamedTemporaryFile
|
|
71
|
+
|
|
72
|
+
# Create a temporary test file
|
|
73
|
+
test_code = '''"""Test type() to typeof conversion."""
|
|
74
|
+
|
|
75
|
+
cl def check_types() {
|
|
76
|
+
let x = 42;
|
|
77
|
+
let y = "hello";
|
|
78
|
+
let z = True;
|
|
79
|
+
|
|
80
|
+
let t1 = type(x);
|
|
81
|
+
let t2 = type(y);
|
|
82
|
+
let t3 = type(z);
|
|
83
|
+
let t4 = type("world");
|
|
84
|
+
|
|
85
|
+
return t1;
|
|
86
|
+
}
|
|
87
|
+
'''
|
|
88
|
+
|
|
89
|
+
with NamedTemporaryFile(mode='w', suffix='.jac', delete=False) as f:
|
|
90
|
+
f.write(test_code)
|
|
91
|
+
f.flush()
|
|
92
|
+
|
|
93
|
+
prog = JacProgram()
|
|
94
|
+
module = prog.compile(f.name)
|
|
95
|
+
|
|
96
|
+
assert module.gen.js.strip(), "Expected JavaScript output for client code"
|
|
97
|
+
|
|
98
|
+
# Verify type() was converted to typeof
|
|
99
|
+
assert "typeof" in module.gen.js, "type() should be converted to typeof"
|
|
100
|
+
assert module.gen.js.count("typeof") == 4, "Should have 4 typeof expressions"
|
|
101
|
+
|
|
102
|
+
# Verify no type() calls remain
|
|
103
|
+
assert "type(" not in module.gen.js, "No type() calls should remain in JavaScript"
|
|
104
|
+
|
|
105
|
+
# Verify the typeof expressions are correctly formed
|
|
106
|
+
assert "typeof x" in module.gen.js
|
|
107
|
+
assert "typeof y" in module.gen.js
|
|
108
|
+
assert "typeof z" in module.gen.js
|
|
109
|
+
assert 'typeof "world"' in module.gen.js
|
|
110
|
+
|
|
111
|
+
# Clean up
|
|
112
|
+
import os
|
|
113
|
+
os.unlink(f.name)
|
|
@@ -17,6 +17,9 @@ class TestLoader(TestCase):
|
|
|
17
17
|
|
|
18
18
|
def test_import_basic_python(self) -> None:
|
|
19
19
|
"""Test basic self loading."""
|
|
20
|
+
Jac.reset_machine()
|
|
21
|
+
sys.modules.pop("fixtures", None)
|
|
22
|
+
sys.modules.pop("fixtures.hello_world", None)
|
|
20
23
|
Jac.set_base_path(self.fixture_abs_path(__file__))
|
|
21
24
|
JacMachineInterface.attach_program(
|
|
22
25
|
JacProgram(),
|
|
@@ -26,6 +29,9 @@ class TestLoader(TestCase):
|
|
|
26
29
|
|
|
27
30
|
def test_modules_correct(self) -> None:
|
|
28
31
|
"""Test basic self loading."""
|
|
32
|
+
Jac.reset_machine()
|
|
33
|
+
sys.modules.pop("fixtures", None)
|
|
34
|
+
sys.modules.pop("fixtures.hello_world", None)
|
|
29
35
|
Jac.set_base_path(self.fixture_abs_path(__file__))
|
|
30
36
|
JacMachineInterface.attach_program(
|
|
31
37
|
JacProgram(),
|
|
@@ -141,29 +147,25 @@ class TestLoader(TestCase):
|
|
|
141
147
|
try:
|
|
142
148
|
os.chdir(os.path.dirname(self.fixture_abs_path("jac_import_py_files.py")))
|
|
143
149
|
Jac.set_base_path(self.fixture_abs_path("jac_import_py_files.py"))
|
|
144
|
-
JacMachineInterface.attach_program(
|
|
145
|
-
program:=JacProgram(),
|
|
146
|
-
)
|
|
150
|
+
JacMachineInterface.attach_program(JacProgram())
|
|
147
151
|
Jac.jac_import("jac_import_py_files", base_path=self.fixture_abs_path("jac_import_py_files.py"), lng="py")
|
|
148
152
|
cli.run(self.fixture_abs_path("jac_import_py_files.py"))
|
|
149
153
|
sys.stdout = sys.__stdout__
|
|
150
154
|
stdout_value = captured_output.getvalue()
|
|
151
155
|
self.assertIn("This is main test file for jac import of python files", stdout_value)
|
|
152
|
-
self.assertIn("python_module <jaclang.compiler.unitree.Module object", str(program.mod.hub))
|
|
153
|
-
self.assertIn("jac_module <jaclang.compiler.unitree.Module object", str(program.mod.hub))
|
|
156
|
+
self.assertIn("python_module <jaclang.compiler.unitree.Module object", str(Jac.program.mod.hub))
|
|
157
|
+
self.assertIn("jac_module <jaclang.compiler.unitree.Module object", str(Jac.program.mod.hub))
|
|
154
158
|
os.environ["JAC_PYFILE_RAISE"] = "false"
|
|
155
159
|
settings.load_env_vars()
|
|
156
160
|
os.chdir(os.path.dirname(self.fixture_abs_path("jac_import_py_files.py")))
|
|
157
161
|
Jac.set_base_path(self.fixture_abs_path("jac_import_py_files.py"))
|
|
158
|
-
JacMachineInterface.attach_program(
|
|
159
|
-
program:=JacProgram(),
|
|
160
|
-
)
|
|
162
|
+
JacMachineInterface.attach_program(JacProgram())
|
|
161
163
|
Jac.jac_import("jac_import_py_files", base_path=self.fixture_abs_path("jac_import_py_files.py"), lng="py")
|
|
162
164
|
cli.run(self.fixture_abs_path("jac_import_py_files.py"))
|
|
163
165
|
sys.stdout = sys.__stdout__
|
|
164
166
|
stdout_value = captured_output.getvalue()
|
|
165
167
|
self.assertIn("This is main test file for jac import of python files", stdout_value)
|
|
166
|
-
self.assertNotIn("python_module <jaclang.compiler.unitree.Module object", str(program.mod.hub))
|
|
167
|
-
self.
|
|
168
|
+
self.assertNotIn("python_module <jaclang.compiler.unitree.Module object", str(Jac.program.mod.hub))
|
|
169
|
+
self.assertIn("jac_module <jaclang.compiler.unitree.Module object", str(Jac.program.mod.hub))
|
|
168
170
|
finally:
|
|
169
171
|
os.chdir(original_cwd)
|
|
@@ -4,6 +4,7 @@ import inspect
|
|
|
4
4
|
import io
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from jaclang import JacMachineInterface as Jac
|
|
9
10
|
from jaclang.compiler import jac_lark as jl
|
|
@@ -215,12 +216,12 @@ class TestLarkParser(TestCaseMicroSuite):
|
|
|
215
216
|
}
|
|
216
217
|
""",
|
|
217
218
|
"""
|
|
218
|
-
|
|
219
|
+
Unexpected token 'bar'
|
|
220
|
+
with entry {
|
|
219
221
|
foo = Foo(;
|
|
220
222
|
func(foo bar)
|
|
223
|
+
^^^
|
|
221
224
|
foo.bar;
|
|
222
|
-
^^^
|
|
223
|
-
}
|
|
224
225
|
"""
|
|
225
226
|
]
|
|
226
227
|
for idx, alrt in enumerate(prog.errors_had):
|
|
@@ -229,4 +230,249 @@ class TestLarkParser(TestCaseMicroSuite):
|
|
|
229
230
|
line = line.strip()
|
|
230
231
|
self.assertIn(line, pretty)
|
|
231
232
|
|
|
233
|
+
def _load_combined_jsx_fixture(self) -> tuple[str, JacParser]:
|
|
234
|
+
"""Parse the consolidated JSX fixture once for downstream assertions."""
|
|
235
|
+
fixture_path = (
|
|
236
|
+
Path(__file__)
|
|
237
|
+
.resolve()
|
|
238
|
+
.parent
|
|
239
|
+
.parent
|
|
240
|
+
/ "passes"
|
|
241
|
+
/ "ecmascript"
|
|
242
|
+
/ "tests"
|
|
243
|
+
/ "fixtures"
|
|
244
|
+
/ "client_jsx.jac"
|
|
245
|
+
)
|
|
246
|
+
source_text = fixture_path.read_text(encoding="utf-8")
|
|
247
|
+
prse = JacParser(
|
|
248
|
+
root_ir=Source(source_text, mod_path=str(fixture_path)),
|
|
249
|
+
prog=JacProgram(),
|
|
250
|
+
)
|
|
251
|
+
self.assertFalse(
|
|
252
|
+
prse.errors_had,
|
|
253
|
+
f"Parser reported errors for JSX fixture: {[str(e) for e in prse.errors_had]}",
|
|
254
|
+
)
|
|
255
|
+
return source_text, prse
|
|
256
|
+
|
|
257
|
+
def test_jsx_comprehensive_fixture(self) -> None:
|
|
258
|
+
"""Ensure the consolidated JSX fixture exercises varied grammar shapes."""
|
|
259
|
+
source_text, prse = self._load_combined_jsx_fixture()
|
|
260
|
+
tree_repr = prse.ir_out.pp()
|
|
261
|
+
|
|
262
|
+
expected_snippets = {
|
|
263
|
+
"self_closing": "<div />",
|
|
264
|
+
"attribute_binding": 'id={name}',
|
|
265
|
+
"namespaced_component": "<Form.Input.Text />",
|
|
266
|
+
"fragment": "<>",
|
|
267
|
+
"spread_attribute": "{...props}",
|
|
268
|
+
"expression_child": '{"Hello " + name + "!"}',
|
|
269
|
+
}
|
|
270
|
+
for label, snippet in expected_snippets.items():
|
|
271
|
+
with self.subTest(label=label):
|
|
272
|
+
self.assertIn(snippet, source_text)
|
|
273
|
+
|
|
274
|
+
ast_markers = {
|
|
275
|
+
"JsxElement": "JsxElement" in tree_repr,
|
|
276
|
+
"FragmentTokens": "Token - <>" in tree_repr and "Token - </>" in tree_repr,
|
|
277
|
+
"JsxSpreadAttribute": "JsxSpreadAttribute" in tree_repr,
|
|
278
|
+
}
|
|
279
|
+
for label, present in ast_markers.items():
|
|
280
|
+
with self.subTest(node=label):
|
|
281
|
+
self.assertTrue(present, f"{label} missing from AST pretty print")
|
|
282
|
+
|
|
283
|
+
def test_client_keyword_tagging(self) -> None:
|
|
284
|
+
"""Test that cl keyword properly tags elements as client declarations.
|
|
285
|
+
|
|
286
|
+
Tests:
|
|
287
|
+
- Single statement with cl prefix
|
|
288
|
+
- Statement without cl prefix
|
|
289
|
+
- Block of statements with cl { }
|
|
290
|
+
- Empty cl blocks
|
|
291
|
+
- Multiple cl blocks at top level
|
|
292
|
+
- Various statement types (import, let, obj, test)
|
|
293
|
+
"""
|
|
294
|
+
# Test 1: Mixed single and block client markers
|
|
295
|
+
source = """
|
|
296
|
+
cl let foo = 1;
|
|
297
|
+
let bar = 2;
|
|
298
|
+
cl {
|
|
299
|
+
let baz = 3;
|
|
300
|
+
test sample {}
|
|
301
|
+
}
|
|
302
|
+
"""
|
|
303
|
+
module = JacProgram().parse_str(source, "test.jac")
|
|
304
|
+
body = module.body
|
|
305
|
+
|
|
306
|
+
self.assertEqual(
|
|
307
|
+
[type(stmt).__name__ for stmt in body],
|
|
308
|
+
["GlobalVars", "GlobalVars", "GlobalVars", "Test"],
|
|
309
|
+
)
|
|
310
|
+
self.assertEqual(
|
|
311
|
+
[getattr(stmt, "is_client_decl", False) for stmt in body],
|
|
312
|
+
[True, False, True, True], # cl let, let, cl{let}, cl{test}
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Test 2: Block with different statement types
|
|
316
|
+
source = """
|
|
317
|
+
cl {
|
|
318
|
+
import foo;
|
|
319
|
+
let x = 1;
|
|
320
|
+
obj MyClass {}
|
|
321
|
+
test my_test {}
|
|
322
|
+
}
|
|
323
|
+
"""
|
|
324
|
+
module = JacProgram().parse_str(source, "test.jac")
|
|
325
|
+
body = module.body
|
|
326
|
+
|
|
327
|
+
self.assertEqual(len(body), 4)
|
|
328
|
+
self.assertTrue(
|
|
329
|
+
all(
|
|
330
|
+
getattr(stmt, "is_client_decl", False)
|
|
331
|
+
for stmt in body
|
|
332
|
+
if hasattr(stmt, "is_client_decl")
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Test 3: Multiple cl blocks at top level
|
|
337
|
+
source = """
|
|
338
|
+
cl {
|
|
339
|
+
let a = 1;
|
|
340
|
+
}
|
|
341
|
+
let b = 2;
|
|
342
|
+
cl {
|
|
343
|
+
let c = 3;
|
|
344
|
+
}
|
|
345
|
+
"""
|
|
346
|
+
module = JacProgram().parse_str(source, "test.jac")
|
|
347
|
+
body = module.body
|
|
348
|
+
|
|
349
|
+
self.assertEqual(len(body), 3)
|
|
350
|
+
self.assertEqual(
|
|
351
|
+
[getattr(stmt, "is_client_decl", False) for stmt in body],
|
|
352
|
+
[True, False, True], # cl{let a}, let b, cl{let c}
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Test 4: Empty client block
|
|
356
|
+
source = """
|
|
357
|
+
cl {}
|
|
358
|
+
let x = 1;
|
|
359
|
+
"""
|
|
360
|
+
module = JacProgram().parse_str(source, "test.jac")
|
|
361
|
+
body = module.body
|
|
362
|
+
|
|
363
|
+
self.assertEqual(len(body), 1)
|
|
364
|
+
self.assertFalse(getattr(body[0], "is_client_decl", False))
|
|
365
|
+
|
|
366
|
+
# Test 5: Various statement types with single cl marker
|
|
367
|
+
source = """
|
|
368
|
+
cl import foo;
|
|
369
|
+
cl obj MyClass {}
|
|
370
|
+
cl test my_test {}
|
|
371
|
+
"""
|
|
372
|
+
module = JacProgram().parse_str(source, "test.jac")
|
|
373
|
+
body = module.body
|
|
374
|
+
|
|
375
|
+
self.assertEqual(len(body), 3)
|
|
376
|
+
self.assertTrue(
|
|
377
|
+
all(
|
|
378
|
+
getattr(stmt, "is_client_decl", False)
|
|
379
|
+
for stmt in body
|
|
380
|
+
if hasattr(stmt, "is_client_decl")
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def test_anonymous_ability_decl(self) -> None:
|
|
385
|
+
"""Test that abilities can be declared without explicit names.
|
|
386
|
+
|
|
387
|
+
Tests:
|
|
388
|
+
- Anonymous ability with entry event
|
|
389
|
+
- Anonymous ability with exit event
|
|
390
|
+
- Named ability still works
|
|
391
|
+
- Autogenerated names are unique based on location
|
|
392
|
+
"""
|
|
393
|
+
# Test 1: Anonymous ability with entry event
|
|
394
|
+
source = """
|
|
395
|
+
walker MyWalker {
|
|
396
|
+
can with entry {
|
|
397
|
+
print("hello");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
"""
|
|
401
|
+
prog = JacProgram()
|
|
402
|
+
module = prog.parse_str(source, "test.jac")
|
|
403
|
+
self.assertFalse(prog.errors_had)
|
|
404
|
+
|
|
405
|
+
# Find the walker and its ability
|
|
406
|
+
walker = module.body[0]
|
|
407
|
+
abilities = [stmt for stmt in walker.body if type(stmt).__name__ == "Ability"]
|
|
408
|
+
self.assertEqual(len(abilities), 1)
|
|
409
|
+
|
|
410
|
+
ability = abilities[0]
|
|
411
|
+
self.assertIsNone(ability.name_ref)
|
|
412
|
+
# Check that py_resolve_name generates a name
|
|
413
|
+
resolved_name = ability.py_resolve_name()
|
|
414
|
+
self.assertTrue(resolved_name.startswith("__ability_entry_"))
|
|
415
|
+
self.assertTrue(resolved_name.endswith("__"))
|
|
416
|
+
|
|
417
|
+
# Test 2: Anonymous ability with exit event
|
|
418
|
+
source = """
|
|
419
|
+
walker MyWalker {
|
|
420
|
+
can with exit {
|
|
421
|
+
print("goodbye");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
"""
|
|
425
|
+
prog = JacProgram()
|
|
426
|
+
module = prog.parse_str(source, "test.jac")
|
|
427
|
+
self.assertFalse(prog.errors_had)
|
|
428
|
+
|
|
429
|
+
walker = module.body[0]
|
|
430
|
+
abilities = [stmt for stmt in walker.body if type(stmt).__name__ == "Ability"]
|
|
431
|
+
ability = abilities[0]
|
|
432
|
+
resolved_name = ability.py_resolve_name()
|
|
433
|
+
self.assertTrue(resolved_name.startswith("__ability_exit_"))
|
|
434
|
+
|
|
435
|
+
# Test 3: Named ability still works
|
|
436
|
+
source = """
|
|
437
|
+
walker MyWalker {
|
|
438
|
+
can my_ability with entry {
|
|
439
|
+
print("named");
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
"""
|
|
443
|
+
prog = JacProgram()
|
|
444
|
+
module = prog.parse_str(source, "test.jac")
|
|
445
|
+
self.assertFalse(prog.errors_had)
|
|
446
|
+
|
|
447
|
+
walker = module.body[0]
|
|
448
|
+
abilities = [stmt for stmt in walker.body if type(stmt).__name__ == "Ability"]
|
|
449
|
+
ability = abilities[0]
|
|
450
|
+
self.assertIsNotNone(ability.name_ref)
|
|
451
|
+
self.assertEqual(ability.py_resolve_name(), "my_ability")
|
|
452
|
+
|
|
453
|
+
# Test 4: Multiple anonymous abilities generate unique names
|
|
454
|
+
source = """
|
|
455
|
+
walker MyWalker {
|
|
456
|
+
can with entry {
|
|
457
|
+
print("first");
|
|
458
|
+
}
|
|
459
|
+
can with entry {
|
|
460
|
+
print("second");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
"""
|
|
464
|
+
prog = JacProgram()
|
|
465
|
+
module = prog.parse_str(source, "test.jac")
|
|
466
|
+
self.assertFalse(prog.errors_had)
|
|
467
|
+
|
|
468
|
+
walker = module.body[0]
|
|
469
|
+
abilities = [stmt for stmt in walker.body if type(stmt).__name__ == "Ability"]
|
|
470
|
+
self.assertEqual(len(abilities), 2)
|
|
471
|
+
|
|
472
|
+
name1 = abilities[0].py_resolve_name()
|
|
473
|
+
name2 = abilities[1].py_resolve_name()
|
|
474
|
+
# Names should be different due to different locations
|
|
475
|
+
self.assertNotEqual(name1, name2)
|
|
476
|
+
|
|
477
|
+
|
|
232
478
|
TestLarkParser.self_attach_micro_tests()
|