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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (103) hide show
  1. jaclang/cli/cli.py +147 -25
  2. jaclang/cli/cmdreg.py +144 -8
  3. jaclang/compiler/__init__.py +6 -1
  4. jaclang/compiler/codeinfo.py +16 -1
  5. jaclang/compiler/constant.py +33 -13
  6. jaclang/compiler/jac.lark +130 -31
  7. jaclang/compiler/larkparse/jac_parser.py +2 -2
  8. jaclang/compiler/parser.py +567 -176
  9. jaclang/compiler/passes/__init__.py +2 -1
  10. jaclang/compiler/passes/ast_gen/__init__.py +5 -0
  11. jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
  12. jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
  13. jaclang/compiler/passes/ecmascript/__init__.py +25 -0
  14. jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
  15. jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
  16. jaclang/compiler/passes/ecmascript/estree.py +972 -0
  17. jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
  18. jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
  19. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
  20. jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
  21. jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
  22. jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
  23. jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
  24. jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
  25. jaclang/compiler/passes/main/__init__.py +0 -3
  26. jaclang/compiler/passes/main/annex_pass.py +23 -1
  27. jaclang/compiler/passes/main/pyast_gen_pass.py +324 -234
  28. jaclang/compiler/passes/main/pyast_load_pass.py +46 -11
  29. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
  30. jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
  31. jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
  32. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
  33. jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
  34. jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
  35. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
  36. jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -2
  37. jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
  38. jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
  39. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
  40. jaclang/compiler/passes/main/type_checker_pass.py +7 -0
  41. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +115 -0
  42. jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
  43. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
  44. jaclang/compiler/passes/transform.py +9 -1
  45. jaclang/compiler/passes/uni_pass.py +5 -7
  46. jaclang/compiler/program.py +22 -25
  47. jaclang/compiler/tests/test_client_codegen.py +113 -0
  48. jaclang/compiler/tests/test_importer.py +12 -10
  49. jaclang/compiler/tests/test_parser.py +249 -3
  50. jaclang/compiler/type_system/type_evaluator.jac +169 -50
  51. jaclang/compiler/type_system/type_utils.py +1 -1
  52. jaclang/compiler/type_system/types.py +6 -0
  53. jaclang/compiler/unitree.py +430 -84
  54. jaclang/langserve/engine.jac +224 -288
  55. jaclang/langserve/sem_manager.jac +12 -8
  56. jaclang/langserve/server.jac +48 -48
  57. jaclang/langserve/tests/fixtures/greet.py +17 -0
  58. jaclang/langserve/tests/fixtures/md_path.jac +22 -0
  59. jaclang/langserve/tests/fixtures/user.jac +15 -0
  60. jaclang/langserve/tests/test_server.py +66 -371
  61. jaclang/lib.py +1 -1
  62. jaclang/runtimelib/client_bundle.py +169 -0
  63. jaclang/runtimelib/client_runtime.jac +586 -0
  64. jaclang/runtimelib/constructs.py +2 -0
  65. jaclang/runtimelib/machine.py +259 -100
  66. jaclang/runtimelib/meta_importer.py +111 -22
  67. jaclang/runtimelib/mtp.py +15 -0
  68. jaclang/runtimelib/server.py +1089 -0
  69. jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
  70. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  71. jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
  72. jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
  73. jaclang/runtimelib/tests/test_client_bundle.py +55 -0
  74. jaclang/runtimelib/tests/test_client_render.py +63 -0
  75. jaclang/runtimelib/tests/test_serve.py +1069 -0
  76. jaclang/settings.py +0 -2
  77. jaclang/tests/fixtures/iife_functions.jac +142 -0
  78. jaclang/tests/fixtures/iife_functions_client.jac +143 -0
  79. jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
  80. jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
  81. jaclang/tests/fixtures/needs_import_dup.jac +6 -4
  82. jaclang/tests/fixtures/py_run.py +7 -5
  83. jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
  84. jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
  85. jaclang/tests/test_cli.py +1 -1
  86. jaclang/tests/test_language.py +10 -39
  87. jaclang/tests/test_reference.py +17 -2
  88. jaclang/utils/NonGPT.py +375 -0
  89. jaclang/utils/helpers.py +44 -16
  90. jaclang/utils/lang_tools.py +31 -4
  91. jaclang/utils/tests/test_lang_tools.py +1 -1
  92. jaclang/utils/treeprinter.py +8 -3
  93. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
  94. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/RECORD +96 -66
  95. jaclang/compiler/passes/main/binder_pass.py +0 -594
  96. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
  97. jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
  98. jaclang/langserve/tests/session.jac +0 -294
  99. jaclang/langserve/tests/test_dev_server.py +0 -80
  100. jaclang/runtimelib/importer.py +0 -351
  101. jaclang/tests/test_typecheck.py +0 -542
  102. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
  103. {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1 @@
1
+ """Tests for ECMAScript AST generation."""
@@ -0,0 +1,170 @@
1
+ """Advanced Jac constructs combined fixture for ECMAScript generator tests."""
2
+
3
+ # Lambdas and higher-order helpers
4
+ def lambda_examples() -> dict {
5
+ adder = lambda a: int, b: int : a + b;
6
+ scaler = lambda value: int, factor: int : value * factor;
7
+ numbers = [1, 2, 3, 4];
8
+ doubled = [scaler(n, 2) for n in numbers];
9
+ filtered = [n for n in numbers if (keep := n % 2 == 0)];
10
+ return {
11
+ "sum": adder(2, 5),
12
+ "doubled": doubled,
13
+ "filtered": filtered
14
+ };
15
+ }
16
+
17
+ # Async / await usage
18
+ async def fetch_value(value: int) -> int {
19
+ return value;
20
+ }
21
+
22
+ async def async_pipeline(data: list) -> dict {
23
+ total = 0;
24
+ results = [];
25
+ for item in data {
26
+ response = await fetch_value(item);
27
+ total += response;
28
+ results.append(response);
29
+ }
30
+ status = "large" if total > 10 else "small";
31
+ return {"total": total, "results": results, "status": status};
32
+ }
33
+
34
+ # Generators
35
+ def generator_examples(limit: int) {
36
+ index = 0;
37
+ while index < limit {
38
+ yield index * 3;
39
+ index += 1;
40
+ }
41
+ }
42
+
43
+ # Spread and rest patterns
44
+ def spread_and_rest_examples() -> dict {
45
+ base = [1, 2, 3];
46
+ extras = [4, 5];
47
+ combined = [*base, *extras, 6];
48
+
49
+ defaults = {"mode": "dev", "retries": 1};
50
+ overrides = {"retries": 3, "timeout": 30};
51
+ merged = {**defaults, **overrides};
52
+
53
+ def collect(label: str, *items: tuple, **options: dict) -> dict {
54
+ return {"label": label, "items": list(items), "options": options};
55
+ }
56
+
57
+ bag = collect("demo", *combined, limit=10, strict=True);
58
+ return {"combined": combined, "merged": merged, "bag": bag};
59
+ }
60
+
61
+ # Pattern matching / switch lowering
62
+ def pattern_matching_examples(code: int, flag: bool) -> str {
63
+ match code {
64
+ case 200:
65
+ return "ok";
66
+
67
+ case 404 | 500:
68
+ return "error";
69
+
70
+ case _:
71
+ if flag {
72
+ return "fallback";
73
+ }
74
+ return "unknown";
75
+
76
+ }
77
+ }
78
+
79
+ # Template literals and complex expressions
80
+ def template_literal_examples(user: str, score: int) -> str {
81
+ status = "pass" if score >= 60 else "fail";
82
+ return f"{user} scored {score} which is a {status}";
83
+ }
84
+
85
+ # Destructuring & rest patterns
86
+ def advanced_destructuring() -> dict {
87
+ (first, *middle, last) = [10, 20, 30, 40, 50];
88
+ point = (100, 200);
89
+ (x, y) = point;
90
+ settings = {"limits": {"max": 5}};
91
+ limit = 0;
92
+ if settings is not None and "limits" in settings {
93
+ inner = settings["limits"];
94
+ if inner is not None and "max" in inner {
95
+ limit = inner["max"];
96
+ }
97
+ }
98
+ return {"first": first, "middle": middle, "last": last, "point": (x, y), "limit": limit};
99
+ }
100
+
101
+ # Optional access simulations
102
+ def optional_access_examples(payload: dict) -> dict {
103
+ result = {"value": 0, "mode": "basic"};
104
+ if payload is not None and "config" in payload {
105
+ config = payload["config"];
106
+ if config is not None and "value" in config {
107
+ result["value"] = config["value"];
108
+ }
109
+ if config is not None and "mode" in config {
110
+ result["mode"] = config["mode"];
111
+ }
112
+ }
113
+ return result;
114
+ }
115
+
116
+ # Update expression stand-ins
117
+ def update_expression_examples() -> dict {
118
+ counter = 0;
119
+ for _ in range(5) {
120
+ counter += 1;
121
+ }
122
+
123
+ index = 3;
124
+ index -= 1;
125
+ return {"counter": counter, "index": index};
126
+ }
127
+
128
+ # Do-while style loops
129
+ def do_while_simulation(limit: int) -> int {
130
+ total = 0;
131
+ current = 0;
132
+ while True {
133
+ total += current;
134
+ current += 1;
135
+ if not (current < limit) {
136
+ break;
137
+ }
138
+ }
139
+ return total;
140
+ }
141
+
142
+ # Combined advanced report
143
+ def build_advanced_report(values: list) -> dict {
144
+ lambda_data = lambda_examples();
145
+ spread_data = spread_and_rest_examples();
146
+ optional = optional_access_examples({"config": {"value": 7, "mode": "strict"}});
147
+ destructured = advanced_destructuring();
148
+
149
+ async def gather_async() -> dict {
150
+ return await async_pipeline(values);
151
+ }
152
+
153
+ generator_list = [];
154
+ for item in generator_examples(len(values)) {
155
+ generator_list.append(item);
156
+ }
157
+
158
+ return {
159
+ "lambda": lambda_data,
160
+ "spread": spread_data,
161
+ "optional": optional,
162
+ "destructured": destructured,
163
+ "pattern": pattern_matching_examples(404, False),
164
+ "template": template_literal_examples("user", 72),
165
+ "updates": update_expression_examples(),
166
+ "loop": do_while_simulation(4),
167
+ "generator": generator_list,
168
+ "async_helper": gather_async
169
+ };
170
+ }
@@ -0,0 +1,30 @@
1
+ """Implementation file for Calculator classes."""
2
+
3
+ impl Calculator.init(initial_value: float) {
4
+ self.value = initial_value;
5
+ }
6
+
7
+ impl Calculator.add(amount: float) -> float {
8
+ self.value = self.value + amount;
9
+ return self.value;
10
+ }
11
+
12
+ impl Calculator.multiply(factor: float) -> float {
13
+ self.value = self.value * factor;
14
+ return self.value;
15
+ }
16
+
17
+ impl Calculator.get_value -> float {
18
+ return self.value;
19
+ }
20
+
21
+ impl ScientificCalculator.power(exponent: float) -> float {
22
+ result = self.value;
23
+ count = 1;
24
+ while count < exponent {
25
+ result = result * self.value;
26
+ count = count + 1;
27
+ }
28
+ self.value = result;
29
+ return self.value;
30
+ }
@@ -0,0 +1,14 @@
1
+ """Test fixture for class with separate impl file."""
2
+
3
+ obj Calculator {
4
+ has value: float;
5
+
6
+ def init(initial_value: float);
7
+ def add(amount: float) -> float;
8
+ def multiply(factor: float) -> float;
9
+ def get_value -> float;
10
+ }
11
+
12
+ obj ScientificCalculator(Calculator) {
13
+ def power(exponent: float) -> float;
14
+ }
@@ -0,0 +1,89 @@
1
+ """Combined JSX fixture covering client declarations and varied JSX patterns."""
2
+
3
+ cl let API_URL: str = "https://api.example.com";
4
+
5
+ cl obj ButtonProps {
6
+ has label: str = "Hello";
7
+ has count: int = 0;
8
+ }
9
+
10
+ cl def component() {
11
+ return <div class="box">
12
+ <h1>Hello</h1>
13
+ <p>Welcome!</p>
14
+ </div>;
15
+ }
16
+
17
+ def server_only() {
18
+ return "not included";
19
+ }
20
+
21
+ with entry {
22
+ # Common values used across scenarios
23
+ let name = "World";
24
+ let age = 30;
25
+ let count = 42;
26
+ let props = "dummy_props";
27
+ let extraProps = "extra_props";
28
+
29
+ # Basic JSX shapes
30
+ let basic_div = <div />;
31
+ let basic_component = <MyComponent />;
32
+ let attribute_button = <button id="submit" disabled />;
33
+ let attr_with_expr = <div id={name} data-age={age}>User Info</div>;
34
+
35
+ # Expressions embedded in JSX
36
+ let greeting = <h1>{"Hello " + name + "!"}</h1>;
37
+ let button = <button id={name} count={count}>{"Click"}</button>;
38
+ let computed = <div>{count + 10}</div>;
39
+
40
+ # Nested elements and layout
41
+ let card = <div>
42
+ <h1>{"Title"}</h1>
43
+ <p>{"Description"}</p>
44
+ <button>{"Click"}</button>
45
+ </div>;
46
+
47
+ let layout = <div>
48
+ <header>
49
+ <nav>
50
+ <a>{"Home"}</a>
51
+ <a>{"About"}</a>
52
+ </nav>
53
+ </header>
54
+ <main>
55
+ <article>{"Content here"}</article>
56
+ </main>
57
+ </div>;
58
+
59
+ # Components and namespaces
60
+ let comp_props = <Button color="blue" size="large">Submit</Button>;
61
+ let comp_namespaced = <UI.Button />;
62
+ let comp_deep_namespace = <Form.Input.Text />;
63
+ let app = <App>
64
+ <Header />
65
+ <Main>
66
+ <Sidebar />
67
+ <Content />
68
+ </Main>
69
+ <Footer />
70
+ </App>;
71
+
72
+ # Fragments
73
+ let empty_fragment = <></>;
74
+ let fragment_with_children = <>
75
+ <h1>{"Title"}</h1>
76
+ <p>{"Paragraph"}</p>
77
+ </>;
78
+ let mixed_fragment = <>
79
+ {"Some text"}
80
+ <div>{"Element"}</div>
81
+ {"More text"}
82
+ <span>{"Inline"}</span>
83
+ </>;
84
+
85
+ # Spread attributes
86
+ let spread_div = <div {...props} />;
87
+ let spread_button = <button {...props} {...extraProps}>{"Click"}</button>;
88
+ let spread_input = <input type="text" {...props} disabled />;
89
+ }
@@ -0,0 +1,195 @@
1
+ """Combined core Jac constructs for ECMAScript generator tests."""
2
+
3
+ let global_counter: int = 0;
4
+
5
+ # Basic functions and control flow
6
+ def add(a: int, b: int) -> int {
7
+ return a + b;
8
+ }
9
+
10
+ def greet(name: str = "World") -> str {
11
+ return "Hello, " + name;
12
+ }
13
+
14
+ def classify_number(value: int) -> str {
15
+ if value < 0 {
16
+ return "negative";
17
+ } elif value == 0 {
18
+ return "zero";
19
+ }
20
+ return "positive";
21
+ }
22
+
23
+ def fibonacci(n: int) -> int {
24
+ if n <= 1 {
25
+ return n;
26
+ }
27
+ return fibonacci(n - 1) + fibonacci(n - 2);
28
+ }
29
+
30
+ def loop_examples(limit: int) -> int {
31
+ total = 0;
32
+ for i in range(limit) {
33
+ if i % 2 == 0 {
34
+ total += i;
35
+ continue;
36
+ }
37
+ total += i * 2;
38
+ }
39
+
40
+ counter = limit;
41
+ while counter > 0 {
42
+ total -= 1;
43
+ counter -= 1;
44
+ }
45
+ return total;
46
+ }
47
+
48
+ # Data structures and assignments
49
+ def collection_examples() -> dict {
50
+ numbers = [1, 2, 3, 4];
51
+ person = {"name": "Alice", "age": 30};
52
+ matrix = [[1, 2], [3, 4]];
53
+ history = {"first": numbers[0], "last": numbers[-1]};
54
+ return {
55
+ "numbers": numbers,
56
+ "person": person,
57
+ "matrix": matrix,
58
+ "history": history
59
+ };
60
+ }
61
+
62
+ def destructuring_examples() -> dict {
63
+ (x, y) = (10, 20);
64
+ [a, b, c] = [1, 2, 3];
65
+ (head, *tail) = [5, 6, 7, 8];
66
+ result = (size := len(tail)) + x + y + a + b + c;
67
+ return {
68
+ "tuple": (x, y),
69
+ "list": [a, b, c],
70
+ "tail": tail,
71
+ "result": result,
72
+ "size": size
73
+ };
74
+ }
75
+
76
+ def assignment_examples(value: int) -> dict {
77
+ current = value;
78
+ current += 5;
79
+ current -= 2;
80
+ current *= 3;
81
+ current /= 2;
82
+ parity = (status := current % 2);
83
+ return {"current": current, "status": status, "parity": parity};
84
+ }
85
+
86
+ # Exception handling
87
+ def safe_division(a: int, b: int) -> float {
88
+ global global_counter;
89
+ try {
90
+ return a / b;
91
+ } except ZeroDivisionError as err {
92
+ global_counter += 1;
93
+ return 0.0;
94
+ } finally {
95
+ global_counter += 1;
96
+ }
97
+ }
98
+
99
+ # Boolean and comparison helpers
100
+ def compare_values(x: int, y: int) -> dict {
101
+ return {
102
+ "lt": x < y,
103
+ "le": x <= y,
104
+ "eq": x == y,
105
+ "ne": x != y,
106
+ "ge": x >= y,
107
+ "gt": x > y,
108
+ "logic": (x < y and y < 100) or not (x == y)
109
+ };
110
+ }
111
+
112
+ # Object definitions
113
+ obj Person {
114
+ has name: str;
115
+ has age: int = 0;
116
+
117
+ def describe -> str {
118
+ return self.name + " (" + str(self.age) + ")";
119
+ }
120
+ }
121
+
122
+ obj Employee(Person) {
123
+ has role: str = "Engineer";
124
+
125
+ def describe -> str {
126
+ return self.name + " - " + self.role;
127
+ }
128
+ }
129
+
130
+ obj Calculator {
131
+ has value: float = 0.0;
132
+
133
+ def add(x: float) -> float {
134
+ self.value += x;
135
+ return self.value;
136
+ }
137
+
138
+ def subtract(x: float) -> float {
139
+ self.value -= x;
140
+ return self.value;
141
+ }
142
+
143
+ def reset {
144
+ self.value = 0.0;
145
+ }
146
+ }
147
+
148
+ obj MathUtils {
149
+ static def square(x: int) -> int {
150
+ return x * x;
151
+ }
152
+ }
153
+
154
+ # Enumerations
155
+ enum Status {
156
+ ACTIVE = "active",
157
+ INACTIVE = "inactive",
158
+ PENDING = "pending"
159
+ }
160
+
161
+ enum Priority {
162
+ LOW = 1,
163
+ MEDIUM = 2,
164
+ HIGH = 3
165
+ }
166
+
167
+ def summarize_status() -> dict {
168
+ states = [Status.ACTIVE, Status.PENDING];
169
+ return {
170
+ "first": states[0],
171
+ "count": len(states),
172
+ "names": ["ACTIVE", "PENDING"]
173
+ };
174
+ }
175
+
176
+ # Utility demonstrating combined features
177
+ def build_report(data: list) -> dict {
178
+ processed = [];
179
+ for item in data {
180
+ if item is None {
181
+ continue;
182
+ }
183
+ processed.append(item * 2);
184
+ }
185
+
186
+ info = collection_examples();
187
+ stats = compare_values(len(processed), len(info["numbers"]));
188
+
189
+ return {
190
+ "processed": processed,
191
+ "info": info,
192
+ "stats": stats,
193
+ "summary": summarize_status()
194
+ };
195
+ }
@@ -0,0 +1,167 @@
1
+ """Test ECMAScript AST generation using consolidated Jac fixtures."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Iterable
6
+
7
+ from jaclang.compiler.passes.ecmascript import EsastGenPass, es_node_to_dict
8
+ from jaclang.compiler.passes.ecmascript import estree as es
9
+ from jaclang.compiler.passes.ecmascript.es_unparse import es_to_js
10
+ from jaclang.compiler.program import JacProgram
11
+ from jaclang.utils.test import TestCase
12
+
13
+
14
+ def walk_es_nodes(node: es.Node) -> Iterable[es.Node]:
15
+ """Yield every ESTree node in a depth-first traversal."""
16
+ yield node
17
+ for value in vars(node).values():
18
+ if isinstance(value, es.Node):
19
+ yield from walk_es_nodes(value)
20
+ elif isinstance(value, list):
21
+ for item in value:
22
+ if isinstance(item, es.Node):
23
+ yield from walk_es_nodes(item)
24
+
25
+
26
+ class EsastGenPassTests(TestCase):
27
+ """Validate ECMAScript AST output from consolidated fixtures."""
28
+
29
+ CORE_FIXTURE = "core_language_features.jac"
30
+ ADVANCED_FIXTURE = "advanced_language_features.jac"
31
+ CLIENT_FIXTURE = "client_jsx.jac"
32
+
33
+ TargetPass = EsastGenPass
34
+
35
+ def get_fixture_path(self, filename: str) -> str:
36
+ """Return absolute path to a fixture file."""
37
+ fixtures_dir = Path(__file__).parent / "fixtures"
38
+ return str(fixtures_dir / filename)
39
+
40
+ def compile_to_esast(self, filename: str) -> es.Program:
41
+ """Compile Jac source to an ESTree program."""
42
+ prog = JacProgram()
43
+ ir = prog.compile(file_path=filename, no_cgen=True)
44
+
45
+ self.assertFalse(
46
+ prog.errors_had,
47
+ f"Compilation errors in {filename}: {[str(e) for e in prog.errors_had]}",
48
+ )
49
+
50
+ es_pass = EsastGenPass(ir, prog)
51
+ es_ir = es_pass.ir_out
52
+
53
+ self.assertTrue(hasattr(es_ir.gen, "es_ast"), "es_ast attribute missing")
54
+ self.assertIsInstance(es_ir.gen.es_ast, es.Program)
55
+ return es_ir.gen.es_ast
56
+
57
+ def test_core_fixture_ast_shape(self) -> None:
58
+ """Core fixture should expose fundamental declarations in the ESTree."""
59
+ es_ast = self.compile_to_esast(self.get_fixture_path(self.CORE_FIXTURE))
60
+
61
+ func_decls = [
62
+ node for node in es_ast.body if isinstance(node, es.FunctionDeclaration)
63
+ ]
64
+ func_names = {func.id.name for func in func_decls if func.id}
65
+ self.assertTrue({"add", "greet", "fibonacci"}.issubset(func_names))
66
+
67
+ class_decls = [
68
+ node for node in es_ast.body if isinstance(node, es.ClassDeclaration)
69
+ ]
70
+ class_names = {cls.id.name for cls in class_decls if cls.id}
71
+ self.assertIn("Person", class_names)
72
+ self.assertIn("Employee", class_names)
73
+
74
+ var_decls = [
75
+ node for node in es_ast.body if isinstance(node, es.VariableDeclaration)
76
+ ]
77
+ self.assertGreaterEqual(len(var_decls), 2, "Expected const enums and globals")
78
+
79
+ ast_json = json.dumps(es_node_to_dict(es_ast))
80
+ self.assertIn("TryStatement", ast_json, "Expected try/except in core fixture")
81
+ self.assertIn(
82
+ "BinaryExpression",
83
+ ast_json,
84
+ "Binary expressions should appear in core fixture",
85
+ )
86
+
87
+ def test_advanced_fixture_contains_async_and_spread_nodes(self) -> None:
88
+ """Advanced fixture should surface async, await, and spread constructs."""
89
+ es_ast = self.compile_to_esast(self.get_fixture_path(self.ADVANCED_FIXTURE))
90
+
91
+ func_names = {
92
+ node.id.name
93
+ for node in es_ast.body
94
+ if isinstance(node, es.FunctionDeclaration) and node.id
95
+ }
96
+ self.assertIn("lambda_examples", func_names)
97
+ self.assertIn("build_advanced_report", func_names)
98
+
99
+ node_types = {type(node).__name__ for node in walk_es_nodes(es_ast)}
100
+ self.assertIn("AwaitExpression", node_types)
101
+ self.assertIn("SpreadElement", node_types)
102
+ self.assertIn("ConditionalExpression", node_types)
103
+
104
+ ast_json = json.dumps(es_node_to_dict(es_ast))
105
+ self.assertIn("CallExpression", ast_json)
106
+ self.assertIn("ReturnStatement", ast_json)
107
+
108
+ def test_client_fixture_generates_client_bundle(self) -> None:
109
+ """Client fixture should retain JSX lowering behaviour."""
110
+ es_ast = self.compile_to_esast(self.get_fixture_path(self.CLIENT_FIXTURE))
111
+ js_code = es_to_js(es_ast)
112
+
113
+ self.assertIn(
114
+ 'const API_URL = "https://api.example.com";',
115
+ js_code,
116
+ "Client global should remain const.",
117
+ )
118
+ self.assertIn("function component()", js_code)
119
+ self.assertIn("__jacJsx", js_code)
120
+ self.assertNotIn("server_only", js_code)
121
+
122
+ def test_es_ast_serializes_to_json(self) -> None:
123
+ """ESTree should serialize cleanly to JSON for downstream tooling."""
124
+ es_ast = self.compile_to_esast(self.get_fixture_path(self.CORE_FIXTURE))
125
+ ast_dict = es_node_to_dict(es_ast)
126
+
127
+ serialized = json.dumps(ast_dict)
128
+ self.assertIn('"type": "Program"', serialized)
129
+ self.assertGreater(len(serialized), 1000)
130
+
131
+ def test_class_separate_impl_file(self) -> None:
132
+ """Test that separate impl files work correctly for class archetypes."""
133
+ es_ast = self.compile_to_esast(
134
+ self.get_fixture_path("class_separate_impl.jac")
135
+ )
136
+ js_code = es_to_js(es_ast)
137
+
138
+ # Check that the Calculator class exists
139
+ class_decls = [
140
+ node for node in es_ast.body if isinstance(node, es.ClassDeclaration)
141
+ ]
142
+ class_names = {cls.id.name for cls in class_decls if cls.id}
143
+ self.assertIn("Calculator", class_names)
144
+ self.assertIn("ScientificCalculator", class_names)
145
+
146
+ # Check that methods from impl file are present
147
+ calculator_class = next(
148
+ (cls for cls in class_decls if cls.id and cls.id.name == "Calculator"),
149
+ None,
150
+ )
151
+ self.assertIsNotNone(calculator_class)
152
+ if calculator_class:
153
+ method_names = {
154
+ m.key.name
155
+ for m in calculator_class.body.body
156
+ if isinstance(m, es.MethodDefinition) and isinstance(m.key, es.Identifier)
157
+ }
158
+ self.assertIn("add", method_names)
159
+ self.assertIn("multiply", method_names)
160
+ self.assertIn("get_value", method_names)
161
+
162
+ # Check JavaScript output contains the methods
163
+ self.assertIn("class Calculator", js_code)
164
+ self.assertIn("class ScientificCalculator", js_code)
165
+ self.assertIn("add(", js_code)
166
+ self.assertIn("multiply(", js_code)
167
+ self.assertIn("power(", js_code)