jaclang 0.8.7__py3-none-any.whl → 0.8.8__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 (89) hide show
  1. jaclang/cli/cli.py +13 -27
  2. jaclang/cli/cmdreg.py +44 -0
  3. jaclang/compiler/constant.py +0 -1
  4. jaclang/compiler/jac.lark +3 -6
  5. jaclang/compiler/larkparse/jac_parser.py +2 -2
  6. jaclang/compiler/parser.py +213 -34
  7. jaclang/compiler/passes/main/__init__.py +2 -4
  8. jaclang/compiler/passes/main/def_use_pass.py +0 -4
  9. jaclang/compiler/passes/main/predynamo_pass.py +221 -0
  10. jaclang/compiler/passes/main/pyast_gen_pass.py +70 -52
  11. jaclang/compiler/passes/main/pyast_load_pass.py +52 -20
  12. jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
  13. jaclang/compiler/passes/main/tests/fixtures/checker/import_sym.jac +2 -0
  14. jaclang/compiler/passes/main/tests/fixtures/checker/import_sym_test.jac +6 -0
  15. jaclang/compiler/passes/main/tests/fixtures/checker/imported_sym.jac +5 -0
  16. jaclang/compiler/passes/main/tests/fixtures/checker_arg_param_match.jac +37 -0
  17. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +18 -0
  18. jaclang/compiler/passes/main/tests/fixtures/checker_cat_is_animal.jac +18 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_float.jac +7 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_param_types.jac +11 -0
  21. jaclang/compiler/passes/main/tests/fixtures/checker_self_type.jac +9 -0
  22. jaclang/compiler/passes/main/tests/fixtures/checker_sym_inherit.jac +42 -0
  23. jaclang/compiler/passes/main/tests/fixtures/predynamo_fix3.jac +43 -0
  24. jaclang/compiler/passes/main/tests/fixtures/predynamo_where_assign.jac +13 -0
  25. jaclang/compiler/passes/main/tests/fixtures/predynamo_where_return.jac +11 -0
  26. jaclang/compiler/passes/main/tests/test_checker_pass.py +191 -0
  27. jaclang/compiler/passes/main/tests/test_predynamo_pass.py +57 -0
  28. jaclang/compiler/passes/main/type_checker_pass.py +29 -73
  29. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +204 -44
  30. jaclang/compiler/passes/tool/jac_formatter_pass.py +119 -69
  31. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +3 -3
  32. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +4 -5
  33. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +171 -11
  34. jaclang/compiler/passes/transform.py +12 -8
  35. jaclang/compiler/program.py +14 -6
  36. jaclang/compiler/tests/fixtures/jac_import_py_files.py +4 -0
  37. jaclang/compiler/tests/fixtures/jac_module.jac +3 -0
  38. jaclang/compiler/tests/fixtures/multiple_syntax_errors.jac +10 -0
  39. jaclang/compiler/tests/fixtures/python_module.py +1 -0
  40. jaclang/compiler/tests/test_importer.py +39 -0
  41. jaclang/compiler/tests/test_parser.py +49 -0
  42. jaclang/compiler/type_system/type_evaluator.py +351 -67
  43. jaclang/compiler/type_system/type_utils.py +246 -0
  44. jaclang/compiler/type_system/types.py +58 -2
  45. jaclang/compiler/unitree.py +79 -94
  46. jaclang/langserve/engine.jac +138 -159
  47. jaclang/langserve/server.jac +25 -1
  48. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  49. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  50. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  51. jaclang/langserve/tests/fixtures/completion_test_err.jac +10 -0
  52. jaclang/langserve/tests/server_test/circle_template.jac +80 -0
  53. jaclang/langserve/tests/server_test/glob_template.jac +4 -0
  54. jaclang/langserve/tests/server_test/test_lang_serve.py +154 -309
  55. jaclang/langserve/tests/server_test/utils.py +153 -116
  56. jaclang/langserve/tests/test_server.py +21 -84
  57. jaclang/langserve/utils.jac +12 -15
  58. jaclang/runtimelib/machine.py +7 -0
  59. jaclang/runtimelib/meta_importer.py +27 -1
  60. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  61. jaclang/runtimelib/tests/fixtures/savable_object.jac +2 -2
  62. jaclang/settings.py +18 -14
  63. jaclang/tests/fixtures/abc_check.jac +3 -3
  64. jaclang/tests/fixtures/arch_rel_import_creation.jac +12 -12
  65. jaclang/tests/fixtures/chandra_bugs2.jac +3 -3
  66. jaclang/tests/fixtures/create_dynamic_archetype.jac +13 -13
  67. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  68. jaclang/tests/fixtures/params/param_syntax_err.jac +9 -0
  69. jaclang/tests/fixtures/params/test_complex_params.jac +42 -0
  70. jaclang/tests/fixtures/params/test_failing_kwonly.jac +207 -0
  71. jaclang/tests/fixtures/params/test_failing_posonly.jac +116 -0
  72. jaclang/tests/fixtures/params/test_failing_varargs.jac +300 -0
  73. jaclang/tests/fixtures/params/test_kwonly_params.jac +29 -0
  74. jaclang/tests/fixtures/py2jac_params.py +8 -0
  75. jaclang/tests/fixtures/run_test.jac +4 -4
  76. jaclang/tests/test_cli.py +37 -1
  77. jaclang/tests/test_language.py +74 -16
  78. jaclang/utils/helpers.py +47 -2
  79. jaclang/utils/module_resolver.py +10 -0
  80. jaclang/utils/test.py +8 -0
  81. jaclang/utils/treeprinter.py +0 -18
  82. {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/METADATA +1 -2
  83. {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/RECORD +85 -60
  84. {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/WHEEL +1 -1
  85. jaclang/compiler/passes/main/inheritance_pass.py +0 -131
  86. jaclang/langserve/dev_engine.jac +0 -645
  87. jaclang/langserve/dev_server.jac +0 -201
  88. jaclang/langserve/tests/server_test/code_test.py +0 -0
  89. {jaclang-0.8.7.dist-info → jaclang-0.8.8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,300 @@
1
+ # Test failing variable arguments cases (*args, **kwargs)
2
+
3
+ def simple_args_only(*args: int) -> int {
4
+ return sum(args);
5
+ }
6
+
7
+ def simple_kwargs_only(**kwargs: str) -> int {
8
+ return len(kwargs);
9
+ }
10
+
11
+ def args_with_required(req: str, *args: int) -> str {
12
+ return f"{req}: {sum(args)}";
13
+ }
14
+
15
+ def kwargs_with_required(req: int, **kwargs: str) -> dict {
16
+ return {"req": req, "kwargs": kwargs};
17
+ }
18
+
19
+ def full_varargs_signature(
20
+ pos: int,
21
+ /,
22
+ reg: str,
23
+ def_param: float = 1.0,
24
+ *args: int,
25
+ kw_req: bool,
26
+ kw_opt: str = "default",
27
+ **kwargs: any
28
+ ) -> dict {
29
+ return {
30
+ "pos": pos, "reg": reg, "def_param": def_param,
31
+ "args": list(args), "kw_req": kw_req, "kw_opt": kw_opt, "kwargs": kwargs
32
+ };
33
+ }
34
+
35
+ def conflicting_names(name: str, *args: str, name_kw: str, **kwargs: str) -> dict {
36
+ return {"name": name, "args": list(args), "name_kw": name_kw, "kwargs": kwargs};
37
+ }
38
+
39
+ def type_sensitive_varargs(*args: int, **kwargs: int) -> dict {
40
+ return {"args_sum": sum(args), "kwargs_sum": sum(kwargs.values())};
41
+ }
42
+
43
+ def edge_case_empty_varargs(required: str, *args: float, **kwargs: bool) -> str {
44
+ return f"{required}: args={len(args)}, kwargs={len(kwargs)}";
45
+ }
46
+
47
+ def double_star_issues(**kwargs: dict) -> int {
48
+ total = 0;
49
+ for (k, v) in kwargs.items() {
50
+ if isinstance(v, dict) {
51
+ total += len(v);
52
+ } else {
53
+ total += 1;
54
+ }
55
+ }
56
+ return total;
57
+ }
58
+
59
+ def additional_varargs_edge_cases() -> None {
60
+ print("\n=== ADDITIONAL VARARGS EDGE CASES ===");
61
+
62
+ # Test 16: Empty containers passed to varargs
63
+ def test_empty_containers(*args: list, **kwargs: dict) -> dict {
64
+ return {"args_count": len(args), "kwargs_count": len(kwargs)};
65
+ }
66
+
67
+ try {
68
+ result = test_empty_containers([], {}, third_arg=[]);
69
+ print("✅ PASS: Empty containers:", result);
70
+ } except Exception as e {
71
+ print("❌ FAIL: Empty containers failed");
72
+ }
73
+
74
+ # Test 17: Nested data structures
75
+ def complex_data_varargs(*args: dict, **kwargs: list) -> dict {
76
+ return {"total_keys": sum(args), "total_items": len(kwargs)};
77
+ }
78
+
79
+ try {
80
+ result = complex_data_varargs(
81
+ 12,
82
+ 16,
83
+ list1=[1, 2, 3],
84
+ list2=[4, 5]
85
+ );
86
+ print("✅ PASS: Complex data structures:", result);
87
+ } except Exception as e {
88
+ print("❌ FAIL: Complex data structures failed");
89
+ }
90
+
91
+ # Test 18: Special character handling in kwargs keys
92
+ def special_chars_kwargs(**kwargs: str) -> dict {
93
+ return {"keys": list(kwargs.keys()), "count": len(kwargs)};
94
+ }
95
+
96
+ try {
97
+ # Note: Most of these won't work as valid identifiers, but let's test what we can
98
+ result = special_chars_kwargs(normal="value", with_underscore="test");
99
+ print("✅ PASS: Special chars in kwargs:", result);
100
+ } except Exception as e {
101
+ print("❌ FAIL: Special chars failed");
102
+ }
103
+
104
+ # Test 19: Very large argument counts
105
+ def stress_test_args(*args: int) -> dict {
106
+ return {"count": len(args), "sum": sum(args), "avg": sum(args)/len(args) if args else 0};
107
+ }
108
+
109
+ try {
110
+ large_args = list(range(100)); # 100 arguments
111
+ result = stress_test_args(*large_args);
112
+ print("✅ PASS: Large arg count:", f"count={result['count']}, avg={result['avg']}");
113
+ } except Exception as e {
114
+ print("❌ FAIL: Large arg count failed");
115
+ }
116
+
117
+ # Test 20: Mixing unpacking with explicit args
118
+ def mixed_unpacking_test(first: str, *args: int, last: str, **kwargs: bool) -> dict {
119
+ _sum = sum(args);
120
+ _kwargs_true_count = 0;
121
+ for v in kwargs.values() {
122
+ if v {
123
+ _kwargs_true_count += 1;
124
+ }
125
+ }
126
+ return {
127
+ "first": first,
128
+ "args_sum": _sum,
129
+ "last": last,
130
+ "kwargs_true_count": _kwargs_true_count
131
+ };
132
+ }
133
+
134
+ try {
135
+ numbers = [10, 20, 30];
136
+ flags = {"debug": True, "verbose": False, "strict": True};
137
+ result = mixed_unpacking_test("start", *numbers, last="end", **flags);
138
+ print("✅ PASS: Mixed unpacking:", result);
139
+ } except Exception as e {
140
+ print("❌ FAIL: Mixed unpacking failed");
141
+ }
142
+
143
+ # Test 21: Recursive function with varargs
144
+ def recursive_varargs(depth: int, *args: str, **kwargs: int) -> dict {
145
+ if depth <= 0 or not args {
146
+ return {"depth": depth, "args": list(args), "kwargs": kwargs};
147
+ }
148
+
149
+ new_args = args[1:]; # Remove first arg
150
+ new_kwargs = {k: v+1 for (k, v) in kwargs.items()}; # Increment all values
151
+
152
+ return recursive_varargs(depth-1, *new_args, **new_kwargs);
153
+ }
154
+
155
+ try {
156
+ result = recursive_varargs(3, "a", "b", "c", "d", x=1, y=2);
157
+ print("✅ PASS: Recursive varargs:", result);
158
+ } except Exception as e {
159
+ print("❌ FAIL: Recursive varargs failed");
160
+ }
161
+ }
162
+
163
+ with entry {
164
+ print("=== TESTING VARIABLE ARGUMENTS FAILURES ===");
165
+
166
+ # Test 1: Wrong types in *args (if type checking enabled)
167
+ try {
168
+ result = simple_args_only(1, 2, "not_int", 4);
169
+ print("❌ FAIL: Should catch mixed types in *args");
170
+ } except Exception as e {
171
+ print("✅ PASS: Caught *args type error");
172
+ }
173
+
174
+ # Test 2: Wrong types in **kwargs (if type checking enabled)
175
+ try {
176
+ result = simple_kwargs_only(12,a="string", b=123, c="string");
177
+ print("❌ FAIL: Should catch mixed types in **kwargs");
178
+ } except Exception as e {
179
+ print("✅ PASS: Caught **kwargs type error");
180
+ }
181
+
182
+ # Test 3: Missing required before *args
183
+ try {
184
+ result = args_with_required(1, 2, 3, apple=90);
185
+ } except Exception as e {
186
+ print("✅ PASS: Caught required param type error");
187
+ }
188
+
189
+ # Test 4: Missing required before **kwargs
190
+ try {
191
+ result = kwargs_with_required(name="test", value="data");
192
+ print("❌ FAIL: Should catch wrong types for required");
193
+ } except Exception as e {
194
+ print("✅ PASS: Caught required before kwargs error");
195
+ }
196
+
197
+ # Test 5: Complex signature - pos-only passed as keyword
198
+ try {
199
+ result = full_varargs_signature(
200
+ pos=1, reg="test", kw_req=True
201
+ );
202
+ print("❌ FAIL: Should reject pos-only as keyword in varargs");
203
+ } except Exception as e {
204
+ print("✅ PASS: Caught pos-only in varargs error");
205
+ }
206
+
207
+ # Test 6: Complex signature - missing required kw-only
208
+ try {
209
+ result = full_varargs_signature(1, "test", 2.0, 10, 20);
210
+ print("❌ FAIL: Should reject missing kw-only in varargs");
211
+ } except Exception as e {
212
+ print("✅ PASS: Caught missing kw-only in varargs");
213
+ }
214
+
215
+ # Test 7: Name conflicts between params and kwargs
216
+ try {
217
+ result = conflicting_names("test", "arg1", name_kw="kw_test", name="conflict");
218
+ print("❌ FAIL: Should catch parameter name conflicts");
219
+ } except Exception as e {
220
+ print("✅ PASS: Caught name conflict error");
221
+ }
222
+
223
+ # Test 8: Type-sensitive varargs with wrong types
224
+ try {
225
+ result = type_sensitive_varargs(1, 2, 3, a="not_int", b=4.5);
226
+ print("❌ FAIL: Should catch type mismatch in kwargs");
227
+ } except Exception as e {
228
+ print("✅ PASS: Caught varargs type sensitivity error");
229
+ }
230
+
231
+
232
+ # Test 9: Double unpacking issues
233
+ try {
234
+ data = {"a": {"nested": "dict"}, "b": "simple"};
235
+ result = double_star_issues(**data);
236
+ print("✅ INFO: Double star result:", result);
237
+ } except Exception as e {
238
+ print("✅ PASS: Caught double star error");
239
+ }
240
+
241
+ # Test 10: Empty varargs but required params missing
242
+ try {
243
+ result = edge_case_empty_varargs();
244
+ print("❌ FAIL: Should catch missing required with empty varargs");
245
+ } except Exception as e {
246
+ print("✅ PASS: Caught missing required in empty varargs");
247
+ }
248
+
249
+
250
+ # Test 11: Keyword argument conflicts with *args collection
251
+ try {
252
+ items = [1, 2, 3];
253
+ result = args_with_required("test", *items, args="conflict");
254
+ print("❌ FAIL: Should catch args name conflict");
255
+ } except Exception as e {
256
+ print("✅ PASS: Caught args conflict error");
257
+ }
258
+
259
+ # Test 12: **kwargs conflicts with explicit keywords
260
+ try {
261
+ extra_data = {"kw_req": True, "extra": "data"};
262
+ result = full_varargs_signature(1, "test", kw_req=False, **extra_data);
263
+ print("❌ FAIL: Should catch kwargs conflict with explicit");
264
+ } except Exception as e {
265
+ print("✅ PASS: Caught kwargs explicit conflict");
266
+ }
267
+
268
+ # Test 13: Valid calls that should work
269
+ try {
270
+ result = simple_args_only(1, 2, 3, 4, 5);
271
+ print("✅ PASS: Valid *args call:", result);
272
+ } except Exception as e {
273
+ print("❌ FAIL: Valid *args call rejected");
274
+ }
275
+
276
+ try {
277
+ result = simple_kwargs_only(a="test", b="data", c="more");
278
+ print("✅ PASS: Valid **kwargs call:", result);
279
+ } except Exception as e {
280
+ print("❌ FAIL: Valid **kwargs call rejected");
281
+ }
282
+
283
+ try {
284
+ result = full_varargs_signature(
285
+ 1, # pos (pos-only)
286
+ "regular", # reg
287
+ 2.5, # def_param
288
+ 10, 20, 30, # *args
289
+ kw_req=True, # kw_req (required kw-only)
290
+ kw_opt="custom", # kw_opt (optional kw-only)
291
+ extra="data", # **kwargs
292
+ more="info"
293
+ );
294
+ print("✅ PASS: Valid complex varargs call keys:", list(result.keys()));
295
+ } except Exception as e {
296
+ print("❌ FAIL: Valid complex varargs rejected");
297
+ }
298
+
299
+ additional_varargs_edge_cases();
300
+ }
@@ -0,0 +1,29 @@
1
+ # Test keyword-only parameters
2
+
3
+ def simple_kwonly(*, x: int) -> int {
4
+ return x;
5
+ }
6
+
7
+ def kwonly_with_defaults(*, x: int = 10, y: str = "def") -> str {
8
+ return f"{x}-{y}";
9
+ }
10
+
11
+ def regular_plus_kwonly(normal: int, *, kw: str) -> str {
12
+ return f"{normal}|{kw}";
13
+ }
14
+
15
+ def mixed_kwonly(pos: int, reg: str = "def", *, kw1: float, kw2: bool = True) -> str {
16
+ return f"{pos}-{reg}-{kw1}-{kw2}";
17
+ }
18
+
19
+ def all_kwonly(*, a: int, b: str = "test", c: float = 1.0) -> str {
20
+ return f"{a}:{b}:{c}";
21
+ }
22
+
23
+ with entry {
24
+ print("KW_SIMPLE:", simple_kwonly(x=42));
25
+ print("KW_DEF:", kwonly_with_defaults(), kwonly_with_defaults(x=20));
26
+ print("REG_KW:", regular_plus_kwonly(10, kw="test"));
27
+ print("MIXED_KW:", mixed_kwonly(1, kw1=2.5), mixed_kwonly(2, "custom", kw1=3.5, kw2=False));
28
+ print("ALL_KW:", all_kwonly(a=100), all_kwonly(a=200, b="hi", c=9.9));
29
+ }
@@ -0,0 +1,8 @@
1
+ from typing_extensions import TypeAlias
2
+ import collections.abc
3
+
4
+ _ClassInfo: TypeAlias = type | tuple[object, ...]
5
+ Sized = collections.abc.Sized
6
+
7
+ def isinstance(obj: object, class_or_tuple: _ClassInfo, /) -> bool: ...
8
+ def len(obj: Sized,astt ,/, z: int, j: str,a= 90) -> int: ...
@@ -1,17 +1,17 @@
1
1
  glob a = 5, b = 2;
2
2
 
3
3
  test t1 {
4
- check almostEqual(a, 6);
4
+ assert almostEqual(a, 6);
5
5
  }
6
6
 
7
7
  test t2 {
8
- check a != b;
8
+ assert a != b;
9
9
  }
10
10
 
11
11
  test t3 {
12
- check "d" in "abc";
12
+ assert "d" in "abc";
13
13
  }
14
14
 
15
15
  test t4 {
16
- check a - b == 3;
16
+ assert a - b == 3;
17
17
  }
jaclang/tests/test_cli.py CHANGED
@@ -7,6 +7,7 @@ import os
7
7
  import re
8
8
  import subprocess
9
9
  import sys
10
+ import tempfile
10
11
  import traceback
11
12
  import unittest
12
13
  from jaclang.cli import cli
@@ -432,6 +433,41 @@ class JacCliTests(TestCase):
432
433
  "sorted(users, key=lambda x: x['email'], reverse=True)", stdout_value
433
434
  )
434
435
 
436
+ def test_param_arg(self) -> None:
437
+ """Test for lambda argument annotation."""
438
+ captured_output = io.StringIO()
439
+ sys.stdout = captured_output
440
+ from jaclang.compiler.program import JacProgram
441
+
442
+ filename = self.fixture_abs_path('../../tests/fixtures/params/test_complex_params.jac')
443
+ cli.jac2py(f"{self.fixture_abs_path('../../tests/fixtures/params/test_complex_params.jac')}")
444
+ py_code = JacProgram().compile(file_path=filename).gen.py
445
+
446
+ # Create temporary Python file
447
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
448
+ temp_file.write(py_code)
449
+ py_file_path = temp_file.name
450
+
451
+ try:
452
+ jac_code = JacProgram().compile(use_str=py_code, file_path=py_file_path).unparse()
453
+ # Create temporary Jac file
454
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jac', delete=False) as temp_file:
455
+ temp_file.write(jac_code)
456
+ jac_file_path = temp_file.name
457
+ cli.run(jac_file_path)
458
+ finally:
459
+ os.remove(py_file_path)
460
+ os.remove(jac_file_path)
461
+
462
+ sys.stdout = sys.__stdout__
463
+ stdout_value = captured_output.getvalue().split("\n")
464
+ self.assertEqual("ULTIMATE_MIN: 1|def|2.5|0|test|100|0", stdout_value[-7])
465
+ self.assertEqual("ULTIMATE_FULL: 1|custom|3.14|3|req|200|1", stdout_value[-6])
466
+ self.assertEqual("SEPARATORS: 42", stdout_value[-5])
467
+ self.assertEqual("EDGE_MIX: 1-test-2-True-1", stdout_value[-4])
468
+ self.assertEqual("RECURSIVE: 7 11", stdout_value[-3])
469
+ self.assertEqual("VALIDATION: x:1,y:2.5,z:10,args:1,w:True,kwargs:1", stdout_value[-2])
470
+
435
471
  def test_caching_issue(self) -> None:
436
472
  """Test for Caching Issue."""
437
473
  test_file = self.fixture_abs_path("test_caching_issue.jac")
@@ -441,7 +477,7 @@ class JacCliTests(TestCase):
441
477
  f.write(
442
478
  f"""
443
479
  test mytest{{
444
- check 10 == {x};
480
+ assert 10 == {x};
445
481
  }}
446
482
  """
447
483
  )
@@ -3,7 +3,7 @@
3
3
  import io
4
4
  import os
5
5
  import sys
6
- import sysconfig
6
+ import pytest
7
7
  import tempfile
8
8
  import subprocess
9
9
  from pathlib import Path
@@ -514,14 +514,13 @@ class JacLanguageTests(TestCase):
514
514
  ),
515
515
  prog=JacProgram(),
516
516
  ).ir_out.unparse()
517
- self.assertIn("def greet2(**kwargs: Any)", output)
517
+ self.assertIn("def greet2( **kwargs: Any) {", output)
518
518
  self.assertEqual(output.count("with entry {"), 14)
519
519
  self.assertIn("assert (x == 5) , 'x should be equal to 5' ;", output)
520
520
  self.assertIn("if not (x == y) {", output)
521
- self.assertIn("def greet2(**kwargs: Any) {", output)
522
- self.assertIn("squares_dict = { x : (x ** 2) for x in numbers };", output)
521
+ self.assertIn("squares_dict = {x : (x ** 2) for x in numbers};", output)
523
522
  self.assertIn(
524
- '\n\n"""Say hello"""\n@ my_decorator\n\n def say_hello() {', output
523
+ '\n\n"""Say hello"""\n@my_decorator\n\n def say_hello() {', output
525
524
  )
526
525
 
527
526
  def test_pyfunc_2(self) -> None:
@@ -589,6 +588,27 @@ class JacLanguageTests(TestCase):
589
588
  self.assertIn("case Container(inner = Inner(x = a, y = b)):\n", output)
590
589
  self.assertIn("case _:\n", output)
591
590
 
591
+ def test_py2jac_params(self) -> None:
592
+ """Test py ast to Jac ast conversion."""
593
+ from jaclang.compiler.passes.main import PyastBuildPass
594
+ import jaclang.compiler.unitree as ast
595
+ import ast as py_ast
596
+
597
+ py_out_path = os.path.join(self.fixture_abs_path("./"), "py2jac_params.py")
598
+ with open(py_out_path) as f:
599
+ file_source = f.read()
600
+ output = PyastBuildPass(
601
+ ir_in=ast.PythonModuleAst(
602
+ py_ast.parse(file_source),
603
+ orig_src=ast.Source(file_source, py_out_path),
604
+ ),
605
+ prog=None,
606
+ ).ir_out.unparse()
607
+ self.assertIn(
608
+ "def isinstance( <>obj: object , class_or_tuple: _ClassInfo , /) -> bool {", output)
609
+ self.assertIn(
610
+ "def len(<>obj: Sized, astt: Any, /, z: int, j: str, a: Any = 90) -> int {", output)
611
+
592
612
  def test_refs_target(self) -> None:
593
613
  """Test py ast to Jac ast conversion output."""
594
614
  captured_output = io.StringIO()
@@ -712,6 +732,7 @@ class JacLanguageTests(TestCase):
712
732
  mypass = JacProgram().compile(self.fixture_abs_path("byllmissue.jac"))
713
733
  self.assertIn("2:5 - 4:8", mypass.pp())
714
734
 
735
+ @pytest.mark.xfail(reason="TODO: Support symtable for inheritance")
715
736
  def test_inherit_baseclass_sym(self) -> None:
716
737
  """Basic test for symtable support for inheritance."""
717
738
  mypass = JacProgram().compile(
@@ -746,6 +767,43 @@ class JacLanguageTests(TestCase):
746
767
  stdout_value = captured_output.getvalue()
747
768
  self.assertIn("i work", stdout_value)
748
769
 
770
+ def test_kwonly_params(self) -> None:
771
+ """Test importing python."""
772
+ captured_output = io.StringIO()
773
+ sys.stdout = captured_output
774
+ Jac.jac_import("test_kwonly_params", base_path=self.fixture_abs_path("./params"))
775
+ sys.stdout = sys.__stdout__
776
+ stdout_value = captured_output.getvalue().split("\n")
777
+ self.assertEqual("KW_SIMPLE: 42", stdout_value[0])
778
+ self.assertEqual("KW_DEF: 10-def 20-def", stdout_value[1])
779
+ self.assertEqual("REG_KW: 10|test", stdout_value[2])
780
+ self.assertEqual("MIXED_KW: 1-def-2.5-True 2-custom-3.5-False", stdout_value[3])
781
+ self.assertEqual("ALL_KW: 100:test:1.0 200:hi:9.9", stdout_value[4])
782
+
783
+ def test_complex_params(self) -> None:
784
+ """Test importing python."""
785
+ captured_output = io.StringIO()
786
+ sys.stdout = captured_output
787
+ Jac.jac_import("test_complex_params", base_path=self.fixture_abs_path("./params"))
788
+ sys.stdout = sys.__stdout__
789
+ stdout_value = captured_output.getvalue().split("\n")
790
+ self.assertEqual("ULTIMATE_MIN: 1|def|2.5|0|test|100|0", stdout_value[0])
791
+ self.assertEqual("ULTIMATE_FULL: 1|custom|3.14|3|req|200|1", stdout_value[1])
792
+ self.assertEqual("SEPARATORS: 42", stdout_value[2])
793
+ self.assertEqual("EDGE_MIX: 1-test-2-True-1", stdout_value[3])
794
+ self.assertEqual("RECURSIVE: 7 11", stdout_value[4])
795
+ self.assertEqual("VALIDATION: x:1,y:2.5,z:10,args:1,w:True,kwargs:1", stdout_value[5])
796
+
797
+ def test_param_failing(self) -> None:
798
+ """Test importing python."""
799
+ captured_output = io.StringIO()
800
+ sys.stdout = captured_output
801
+ for i in ["test_failing_posonly", "test_failing_kwonly", "test_failing_varargs"]:
802
+ Jac.jac_import(i, base_path=self.fixture_abs_path("./params"))
803
+ sys.stdout = sys.__stdout__
804
+ stdout_value = captured_output.getvalue()
805
+ self.assertNotIn('FAILED', stdout_value)
806
+
749
807
  def test_double_import_exec(self) -> None:
750
808
  """Test importing python."""
751
809
  captured_output = io.StringIO()
@@ -1238,9 +1296,9 @@ class JacLanguageTests(TestCase):
1238
1296
  ).ir_out.unparse()
1239
1297
  self.assertIn("(prev_token_index is None)", output)
1240
1298
  self.assertIn("(next_token_index is None)", output)
1241
- self.assertIn("(tok[ 0 ] > change_end_line)", output)
1242
- self.assertIn("(tok[ 0 ] == change_end_line)", output)
1243
- self.assertIn("(tok[ 1 ] > change_end_char)", output)
1299
+ self.assertIn("(tok[0] > change_end_line)", output)
1300
+ self.assertIn("(tok[0] == change_end_line)", output)
1301
+ self.assertIn("(tok[1] > change_end_char)", output)
1244
1302
 
1245
1303
  def test_here_visitor_usage(self) -> None:
1246
1304
  """Test visitor, here keyword usage in jaclang."""
@@ -1378,7 +1436,7 @@ class JacLanguageTests(TestCase):
1378
1436
  test_content = "Hello, 世界! 🌍 Testing UTF-8 encoding."
1379
1437
  f.write(test_content)
1380
1438
  temp_path = f.name
1381
-
1439
+
1382
1440
  try:
1383
1441
  result = read_file_with_encoding(temp_path)
1384
1442
  self.assertEqual(result, test_content)
@@ -1391,7 +1449,7 @@ class JacLanguageTests(TestCase):
1391
1449
  test_content = "Hello, 世界! UTF-16 encoding test."
1392
1450
  f.write(test_content)
1393
1451
  temp_path = f.name
1394
-
1452
+
1395
1453
  try:
1396
1454
  result = read_file_with_encoding(temp_path)
1397
1455
  self.assertEqual(result, test_content)
@@ -1404,7 +1462,7 @@ class JacLanguageTests(TestCase):
1404
1462
  test_content = "Hello, UTF-8 BOM test! 🚀"
1405
1463
  f.write(test_content)
1406
1464
  temp_path = f.name
1407
-
1465
+
1408
1466
  try:
1409
1467
  result = read_file_with_encoding(temp_path)
1410
1468
  self.assertEqual(result, test_content)
@@ -1431,9 +1489,9 @@ class JacLanguageTests(TestCase):
1431
1489
  with tempfile.NamedTemporaryFile(delete=False) as f:
1432
1490
  binary_data = bytes([0xFF, 0xFE, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F])
1433
1491
  f.write(binary_data)
1434
- f.flush()
1492
+ f.flush()
1435
1493
  temp_path = f.name
1436
-
1494
+
1437
1495
  try:
1438
1496
  result = read_file_with_encoding(temp_path)
1439
1497
  self.assertIsInstance(result, str)
@@ -1446,15 +1504,15 @@ class JacLanguageTests(TestCase):
1446
1504
  with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False) as f:
1447
1505
  test_content = (
1448
1506
  "Special chars: åäö ñ ü ç é\n"
1449
- "Symbols: ©®™ §¶†‡•\n"
1507
+ "Symbols: ©®™ §¶†‡•\n"
1450
1508
  "Math: ∑∏∫√±≤≥≠\n"
1451
1509
  "Arrows: ←→↑↓↔\n"
1452
1510
  "Emoji: 😀😍🎉🔥💯\n"
1453
1511
  )
1454
1512
  f.write(test_content)
1455
- f.flush()
1513
+ f.flush()
1456
1514
  temp_path = f.name
1457
-
1515
+
1458
1516
  try:
1459
1517
  result = read_file_with_encoding(temp_path)
1460
1518
 
jaclang/utils/helpers.py CHANGED
@@ -192,6 +192,37 @@ def dump_traceback(e: Exception) -> str:
192
192
  return trace_dump
193
193
 
194
194
 
195
+ # FIXME: Use a proper color library and/or move this somewhere common to jac stack and use it everywhere.
196
+ # Reference: https://gist.github.com/rene-d/9e584a7dd2935d0f461904b9f2950007
197
+ class ANSIColors:
198
+ """ANSI color codes."""
199
+
200
+ BLACK = "\033[0;30m"
201
+ RED = "\033[0;31m"
202
+ GREEN = "\033[0;32m"
203
+ BROWN = "\033[0;33m"
204
+ BLUE = "\033[0;34m"
205
+ PURPLE = "\033[0;35m"
206
+ CYAN = "\033[0;36m"
207
+ LIGHT_GRAY = "\033[0;37m"
208
+ DARK_GRAY = "\033[1;30m"
209
+ LIGHT_RED = "\033[1;31m"
210
+ LIGHT_GREEN = "\033[1;32m"
211
+ YELLOW = "\033[1;33m"
212
+ LIGHT_BLUE = "\033[1;34m"
213
+ LIGHT_PURPLE = "\033[1;35m"
214
+ LIGHT_CYAN = "\033[1;36m"
215
+ LIGHT_WHITE = "\033[1;37m"
216
+ BOLD = "\033[1m"
217
+ FAINT = "\033[2m"
218
+ ITALIC = "\033[3m"
219
+ UNDERLINE = "\033[4m"
220
+ BLINK = "\033[5m"
221
+ NEGATIVE = "\033[7m"
222
+ CROSSED = "\033[9m"
223
+ END = "\033[0m"
224
+
225
+
195
226
  # TODO: After implementing the TextRange (or simillar named) class to mark a text range
196
227
  # refactor the parameter to accept an instace of that text range object.
197
228
  def pretty_print_source_location(
@@ -200,6 +231,8 @@ def pretty_print_source_location(
200
231
  error_line: int,
201
232
  pos_start: int,
202
233
  pos_end: int,
234
+ *,
235
+ colors: bool = False,
203
236
  ) -> str:
204
237
  """Pretty print internal method for the pretty_print method."""
205
238
  # NOTE: The Line numbers and the column numbers are starts with 1.
@@ -241,7 +274,16 @@ def pretty_print_source_location(
241
274
  idx_line_start = idx
242
275
  while idx < len(file_source) and file_source[idx] != "\n":
243
276
  idx += 1 # Run to the line end.
244
- pretty_dump += file_source[idx_line_start:idx]
277
+
278
+ if colors and (curr_line == error_line):
279
+ pretty_dump += (
280
+ file_source[idx_line_start:pos_start]
281
+ + f"{ANSIColors.RED}{file_source[pos_start:pos_end]}{ANSIColors.END}"
282
+ + file_source[pos_end:idx]
283
+ )
284
+ else:
285
+ pretty_dump += file_source[idx_line_start:idx]
286
+
245
287
  pretty_dump += "\n"
246
288
 
247
289
  if curr_line == error_line: # Print the current line with indicator.
@@ -252,7 +294,10 @@ def pretty_print_source_location(
252
294
  spaces += "\t" if file_source[idx_pre] == "\t" else " "
253
295
 
254
296
  err_token_len = pos_end - pos_start
255
- pretty_dump += spaces + ("^" * err_token_len) + "\n"
297
+ underline = "^" * err_token_len
298
+ if colors:
299
+ underline = f"{ANSIColors.RED}{underline}{ANSIColors.END}"
300
+ pretty_dump += spaces + underline + "\n"
256
301
 
257
302
  if idx == len(file_source):
258
303
  break
@@ -30,6 +30,16 @@ def get_jac_search_paths(base_path: Optional[str] = None) -> list[str]:
30
30
  return list(dict.fromkeys(filter(None, paths)))
31
31
 
32
32
 
33
+ # TODO: need to be removed once python modules are fully supported in jac
34
+ def get_py_search_paths(base_path: Optional[str] = None) -> list[str]:
35
+ """Construct a list of paths to search for Python modules."""
36
+ paths = []
37
+ if base_path:
38
+ paths.append(base_path)
39
+ paths.append(os.getcwd())
40
+ return list(dict.fromkeys(filter(None, paths)))
41
+
42
+
33
43
  def _candidate_from(base: str, parts: list[str]) -> Optional[Tuple[str, str]]:
34
44
  candidate = os.path.join(base, *parts)
35
45
  if os.path.isdir(candidate):