jaclang 0.8.4__py3-none-any.whl → 0.8.5__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 (53) hide show
  1. jaclang/cli/cli.py +74 -22
  2. jaclang/compiler/jac.lark +3 -3
  3. jaclang/compiler/larkparse/jac_parser.py +2 -2
  4. jaclang/compiler/parser.py +14 -21
  5. jaclang/compiler/passes/main/__init__.py +3 -1
  6. jaclang/compiler/passes/main/binder_pass.py +594 -0
  7. jaclang/compiler/passes/main/import_pass.py +8 -256
  8. jaclang/compiler/passes/main/inheritance_pass.py +2 -2
  9. jaclang/compiler/passes/main/pyast_gen_pass.py +35 -69
  10. jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
  11. jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
  12. jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
  13. jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
  14. jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
  15. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
  16. jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
  17. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
  18. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +6 -0
  19. jaclang/compiler/program.py +15 -8
  20. jaclang/compiler/tests/test_sr_errors.py +32 -0
  21. jaclang/compiler/unitree.py +21 -15
  22. jaclang/langserve/engine.jac +23 -4
  23. jaclang/langserve/tests/test_server.py +13 -0
  24. jaclang/runtimelib/importer.py +33 -62
  25. jaclang/runtimelib/utils.py +29 -0
  26. jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
  27. jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
  28. jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
  29. jaclang/tests/fixtures/python_run_test.py +19 -0
  30. jaclang/tests/test_cli.py +67 -0
  31. jaclang/tests/test_language.py +96 -1
  32. jaclang/utils/lang_tools.py +3 -3
  33. jaclang/utils/module_resolver.py +90 -0
  34. jaclang/utils/symtable_test_helpers.py +125 -0
  35. jaclang/utils/test.py +3 -4
  36. jaclang/vendor/interegular/__init__.py +34 -0
  37. jaclang/vendor/interegular/comparator.py +163 -0
  38. jaclang/vendor/interegular/fsm.py +1015 -0
  39. jaclang/vendor/interegular/patterns.py +732 -0
  40. jaclang/vendor/interegular/py.typed +0 -0
  41. jaclang/vendor/interegular/utils/__init__.py +15 -0
  42. jaclang/vendor/interegular/utils/simple_parser.py +165 -0
  43. jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
  44. jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
  45. jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
  46. jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
  47. jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
  48. jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
  49. jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
  50. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/METADATA +1 -1
  51. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/RECORD +53 -29
  52. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/WHEEL +0 -0
  53. {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/entry_points.txt +0 -0
@@ -14,6 +14,35 @@ if TYPE_CHECKING:
14
14
  from jaclang.runtimelib.constructs import NodeAnchor, NodeArchetype
15
15
 
16
16
 
17
+ def read_file_with_encoding(file_path: str) -> str:
18
+ """Read file with proper encoding detection."""
19
+ encodings_to_try = [
20
+ "utf-8-sig",
21
+ "utf-8",
22
+ "utf-16",
23
+ "utf-16le",
24
+ "utf-16be",
25
+ # "latin-1", # TODO: Support reading files with Latin-1 encoding
26
+ ]
27
+
28
+ for encoding in encodings_to_try:
29
+ try:
30
+ with open(file_path, "r", encoding=encoding) as f:
31
+ return f.read()
32
+ except UnicodeError:
33
+ continue
34
+ except Exception as e:
35
+ raise IOError(
36
+ f"Could not read file {file_path}: {e}. "
37
+ f"Report this issue: https://github.com/jaseci-labs/jaseci/issues"
38
+ ) from e
39
+
40
+ raise IOError(
41
+ f"Could not read file {file_path} with any encoding. "
42
+ f"Report this issue: https://github.com/jaseci-labs/jaseci/issues"
43
+ )
44
+
45
+
17
46
  @contextmanager
18
47
  def sys_path_context(path: str) -> Iterator[None]:
19
48
  """Add a path to sys.path temporarily."""
@@ -0,0 +1,60 @@
1
+
2
+
3
+
4
+
5
+ if __name__ == "__main__":
6
+
7
+ def foo():
8
+ print("One")
9
+ return "foo"
10
+ foo()
11
+
12
+ condition = True
13
+
14
+ if condition:
15
+
16
+ print("Two")
17
+
18
+ def bar():
19
+ return
20
+
21
+ main_mod = None
22
+ bar()
23
+
24
+ def baz():
25
+ print("Three")
26
+ return "baz"
27
+
28
+ print(baz())
29
+
30
+
31
+ try:
32
+ a = 90
33
+ except FileNotFoundError:
34
+ pass
35
+
36
+
37
+ condition = 10
38
+
39
+
40
+ while condition:
41
+ print("Processing...")
42
+
43
+ while condition:
44
+ print("Four")
45
+ condition -= 10
46
+ break
47
+
48
+ if condition:
49
+
50
+ def foo():
51
+ return
52
+ foo()
53
+ print("Exiting the loop.")
54
+
55
+ if condition:
56
+ print("still +")
57
+ def foo():
58
+ return
59
+
60
+ print("The End.")
@@ -0,0 +1,25 @@
1
+
2
+
3
+
4
+
5
+
6
+ name = 'Peter'
7
+ relation = 'mother'
8
+ mom_name = 'Mary'
9
+
10
+
11
+ print(f"Hello {name}")
12
+ print(f"Hello {name} {name}")
13
+ print(f"{name} squared is {name} {name}")
14
+
15
+
16
+ print(f"{name.upper()}! wrong poem.")
17
+ print(f"Hello {name} , yoo {relation} is {mom_name}. Myself, I am {name}.")
18
+
19
+
20
+
21
+ item = "Apple"
22
+ price = 1.23
23
+
24
+ print(f"Left aligned: {item:<10} | Price: {price:.2f}")
25
+ print(f"{name = } 🤔")
@@ -0,0 +1,33 @@
1
+
2
+
3
+
4
+ def foo(type= 90):
5
+ """This is a function with a docstring."""
6
+ return type
7
+
8
+ print(foo(type=89))
9
+
10
+ def bar(node= 12, *args,**kwargs):
11
+ """This is another function with a docstring."""
12
+ return node, args, kwargs
13
+
14
+ print(str(bar(node=13, a=1, b=2)))
15
+
16
+
17
+ functions = [
18
+
19
+ dict(
20
+ name="replace_lines",
21
+ args=[
22
+ dict(name="text", type="str", default=None),
23
+ dict(name="old", type="str", default=None),
24
+ dict(name="new", type="str", default=None),
25
+ ],
26
+ returns=dict(type="str", default=None),
27
+ ),
28
+ ]
29
+
30
+ print(f"Functions: {functions}")
31
+
32
+ dict = 90
33
+ print(f"Dict: {dict}")
@@ -0,0 +1,19 @@
1
+ """Simple Python test file for jac run command."""
2
+
3
+ print("Hello from Python!")
4
+ print("This is a test Python file.")
5
+
6
+ def main():
7
+ """Main function to demonstrate execution."""
8
+ result = 42
9
+ print(f"Result: {result}")
10
+ return result
11
+
12
+ if __name__ == "__main__":
13
+ main()
14
+ print("Python execution completed.")
15
+
16
+
17
+ from jaclang.tests.fixtures import py_namedexpr
18
+
19
+ py_namedexpr.walrus_example()
jaclang/tests/test_cli.py CHANGED
@@ -35,6 +35,73 @@ class JacCliTests(TestCase):
35
35
 
36
36
  self.assertIn("Hello World!", stdout_value)
37
37
 
38
+ def test_jac_cli_run_python_file(self) -> None:
39
+ """Test running Python files with jac run command."""
40
+ captured_output = io.StringIO()
41
+ sys.stdout = captured_output
42
+
43
+ cli.run(self.fixture_abs_path("python_run_test.py"))
44
+
45
+ sys.stdout = sys.__stdout__
46
+ stdout_value = captured_output.getvalue()
47
+
48
+ self.assertIn("Hello from Python!", stdout_value)
49
+ self.assertIn("This is a test Python file.", stdout_value)
50
+ self.assertIn("Result: 42", stdout_value)
51
+ self.assertIn("Python execution completed.", stdout_value)
52
+ self.assertIn("10", stdout_value)
53
+
54
+ def test_jac_run_py_fstr(self) -> None:
55
+ """Test running Python files with jac run command."""
56
+ captured_output = io.StringIO()
57
+ sys.stdout = captured_output
58
+
59
+ cli.run(self.fixture_abs_path("pyfunc_fstr.py"))
60
+
61
+ sys.stdout = sys.__stdout__
62
+ stdout_value = captured_output.getvalue()
63
+
64
+ self.assertIn("Hello Peter", stdout_value)
65
+ self.assertIn("Hello Peter Peter", stdout_value)
66
+ self.assertIn("Peter squared is Peter Peter", stdout_value)
67
+ self.assertIn("PETER! wrong poem", stdout_value)
68
+ self.assertIn("Hello Peter , yoo mother is Mary. Myself, I am Peter.", stdout_value)
69
+ self.assertIn("Left aligned: Apple | Price: 1.23", stdout_value)
70
+ self.assertIn("name = Peter 🤔", stdout_value)
71
+
72
+ def test_jac_run_py_fmt(self) -> None:
73
+ """Test running Python files with jac run command."""
74
+ captured_output = io.StringIO()
75
+ sys.stdout = captured_output
76
+
77
+ cli.run(self.fixture_abs_path("pyfunc_fmt.py"))
78
+
79
+ sys.stdout = sys.__stdout__
80
+ stdout_value = captured_output.getvalue()
81
+
82
+ self.assertIn("One", stdout_value)
83
+ self.assertIn("Two", stdout_value)
84
+ self.assertIn("Three", stdout_value)
85
+ self.assertIn("baz", stdout_value)
86
+ self.assertIn("Processing...", stdout_value)
87
+ self.assertIn("Four", stdout_value)
88
+ self.assertIn("The End.", stdout_value)
89
+
90
+ def test_jac_run_pyfunc_kwesc(self) -> None:
91
+ """Test running Python files with jac run command."""
92
+ captured_output = io.StringIO()
93
+ sys.stdout = captured_output
94
+
95
+ cli.run(self.fixture_abs_path("pyfunc_kwesc.py"))
96
+
97
+ sys.stdout = sys.__stdout__
98
+ stdout_value = captured_output.getvalue()
99
+ out = stdout_value.split("\n")
100
+ self.assertIn("89", out[0])
101
+ self.assertIn("(13, (), {'a': 1, 'b': 2})", out[1])
102
+ self.assertIn("Functions: [{'name': 'replace_lines'", out[2])
103
+ self.assertIn("Dict: 90", out[3])
104
+
38
105
  def test_jac_cli_alert_based_err(self) -> None:
39
106
  """Basic test for pass."""
40
107
  captured_output = io.StringIO()
@@ -13,6 +13,7 @@ from jaclang import JacMachine as Jac
13
13
  from jaclang.cli import cli
14
14
  from jaclang.compiler.program import JacProgram
15
15
  from jaclang.utils.test import TestCase
16
+ from jaclang.runtimelib.utils import read_file_with_encoding
16
17
 
17
18
 
18
19
  class JacLanguageTests(TestCase):
@@ -1363,4 +1364,98 @@ class JacLanguageTests(TestCase):
1363
1364
  stdout_value = captured_output.getvalue().split("\n")
1364
1365
  self.assertIn("Num: 4", stdout_value[0])
1365
1366
  self.assertIn("Num: 3", stdout_value[1])
1366
- self.assertIn("Completed", stdout_value[2])
1367
+ self.assertIn("Completed", stdout_value[2])
1368
+
1369
+ def test_read_file_with_encoding_utf8(self) -> None:
1370
+ """Test reading UTF-8 encoded file."""
1371
+ with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False) as f:
1372
+ test_content = "Hello, 世界! 🌍 Testing UTF-8 encoding."
1373
+ f.write(test_content)
1374
+ temp_path = f.name
1375
+
1376
+ try:
1377
+ result = read_file_with_encoding(temp_path)
1378
+ self.assertEqual(result, test_content)
1379
+ finally:
1380
+ os.unlink(temp_path)
1381
+
1382
+ def test_read_file_with_encoding_utf16(self) -> None:
1383
+ """Test reading UTF-16 encoded file when UTF-8 fails."""
1384
+ with tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-16") as f:
1385
+ test_content = "Hello, 世界! UTF-16 encoding test."
1386
+ f.write(test_content)
1387
+ temp_path = f.name
1388
+
1389
+ try:
1390
+ result = read_file_with_encoding(temp_path)
1391
+ self.assertEqual(result, test_content)
1392
+ finally:
1393
+ os.unlink(temp_path)
1394
+
1395
+ def test_read_file_with_encoding_utf8_bom(self) -> None:
1396
+ """Test reading UTF-8 with BOM encoded file."""
1397
+ with tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8-sig') as f:
1398
+ test_content = "Hello, UTF-8 BOM test! 🚀"
1399
+ f.write(test_content)
1400
+ temp_path = f.name
1401
+
1402
+ try:
1403
+ result = read_file_with_encoding(temp_path)
1404
+ self.assertEqual(result, test_content)
1405
+ finally:
1406
+ os.unlink(temp_path)
1407
+
1408
+ # TODO: Support reading files with Latin-1 encoding
1409
+ # def test_read_file_with_encoding_latin1(self) -> None:
1410
+ # """Test reading Latin-1 encoded file as fallback."""
1411
+ # with tempfile.NamedTemporaryFile(mode='w', encoding='latin-1', delete=False) as f:
1412
+ # test_content = "Hello, café! Latin-1 test."
1413
+ # f.write(test_content)
1414
+ # f.flush()
1415
+ # temp_path = f.name
1416
+
1417
+ # try:
1418
+ # result = read_file_with_encoding(temp_path)
1419
+ # self.assertEqual(result, test_content)
1420
+ # finally:
1421
+ # os.unlink(temp_path)
1422
+
1423
+ def test_read_file_with_encoding_binary_file_fallback(self) -> None:
1424
+ """Test reading binary file falls back to latin-1."""
1425
+ with tempfile.NamedTemporaryFile(delete=False) as f:
1426
+ binary_data = bytes([0xFF, 0xFE, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F])
1427
+ f.write(binary_data)
1428
+ f.flush()
1429
+ temp_path = f.name
1430
+
1431
+ try:
1432
+ result = read_file_with_encoding(temp_path)
1433
+ self.assertIsInstance(result, str)
1434
+ self.assertGreater(len(result), 0)
1435
+ finally:
1436
+ os.unlink(temp_path)
1437
+
1438
+ def test_read_file_with_encoding_special_characters(self) -> None:
1439
+ """Test reading file with various special characters."""
1440
+ with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False) as f:
1441
+ test_content = (
1442
+ "Special chars: åäö ñ ü ç é\n"
1443
+ "Symbols: ©®™ §¶†‡•\n"
1444
+ "Math: ∑∏∫√±≤≥≠\n"
1445
+ "Arrows: ←→↑↓↔\n"
1446
+ "Emoji: 😀😍🎉🔥💯\n"
1447
+ )
1448
+ f.write(test_content)
1449
+ f.flush()
1450
+ temp_path = f.name
1451
+
1452
+ try:
1453
+ result = read_file_with_encoding(temp_path)
1454
+
1455
+ self.assertEqual(result, test_content)
1456
+ self.assertIn("åäö", result)
1457
+ self.assertIn("©®™", result)
1458
+ self.assertIn("∑∏∫", result)
1459
+ self.assertIn("😀😍", result)
1460
+ finally:
1461
+ os.unlink(temp_path)
@@ -11,6 +11,7 @@ from jaclang.compiler.passes.main import PyastBuildPass
11
11
  from jaclang.compiler.passes.tool.doc_ir_gen_pass import DocIRGenPass
12
12
  from jaclang.compiler.program import JacProgram
13
13
  from jaclang.compiler.unitree import UniScopeNode
14
+ from jaclang.runtimelib.utils import read_file_with_encoding
14
15
  from jaclang.utils.helpers import auto_generate_refs, pascal_to_snake
15
16
 
16
17
 
@@ -194,9 +195,8 @@ class AstTool:
194
195
  base = base if base else "./"
195
196
 
196
197
  if file_name.endswith(".py"):
197
- with open(file_name, "r") as f:
198
- file_source = f.read()
199
- parsed_ast = py_ast.parse(file_source)
198
+ file_source = read_file_with_encoding(file_name)
199
+ parsed_ast = py_ast.parse(file_source)
200
200
  if output == "pyast":
201
201
  return f"\n{py_ast.dump(parsed_ast, indent=2)}"
202
202
  try:
@@ -57,6 +57,18 @@ def resolve_module(target: str, base_path: str) -> Tuple[str, str]:
57
57
  if res:
58
58
  return res
59
59
 
60
+ typeshed_paths = get_typeshed_paths()
61
+ for typeshed_dir in typeshed_paths:
62
+ res = _candidate_from_typeshed(typeshed_dir, actual_parts)
63
+ if res:
64
+ # print(f"Found '{target}' in typeshed: {res[0]}")
65
+ return res
66
+
67
+ # If not found in any typeshed directory, but typeshed is configured,
68
+ # return a stub .pyi path for type checking.
69
+ stub_pyi_path = os.path.join(typeshed_paths[0], *actual_parts) + ".pyi"
70
+ if os.path.isfile(stub_pyi_path):
71
+ return stub_pyi_path, "pyi"
60
72
  base_dir = base_path if os.path.isdir(base_path) else os.path.dirname(base_path)
61
73
  for _ in range(max(level - 1, 0)):
62
74
  base_dir = os.path.dirname(base_dir)
@@ -90,3 +102,81 @@ def resolve_relative_path(target: str, base_path: str) -> str:
90
102
  """Resolve only the path component for a target."""
91
103
  path, _ = resolve_module(target, base_path)
92
104
  return path
105
+
106
+
107
+ def get_typeshed_paths() -> list[str]:
108
+ """Return the typeshed stubs and stdlib directories if available."""
109
+ # You may want to make this configurable or autodetect
110
+ # Corrected base path calculation: removed one ".."
111
+ base = os.path.join(
112
+ os.path.dirname(__file__), # jaclang/utils
113
+ "..", # jaclang
114
+ "vendor",
115
+ "typeshed", # jaclang/vendor/typeshed
116
+ )
117
+ base = os.path.abspath(base)
118
+ stubs = os.path.join(base, "stubs")
119
+ stdlib = os.path.join(base, "stdlib")
120
+ paths = []
121
+ if os.path.isdir(stubs):
122
+ paths.append(stubs)
123
+ if os.path.isdir(stdlib):
124
+ paths.append(stdlib)
125
+ return paths
126
+
127
+
128
+ def _candidate_from_typeshed(base: str, parts: list[str]) -> Optional[Tuple[str, str]]:
129
+ """Find .pyi files in typeshed, trying module.pyi then package/__init__.pyi."""
130
+ if not parts: #
131
+ return None
132
+
133
+ # This is the path prefix for the module/package, e.g., os.path.join(base, "collections", "abc")
134
+ candidate_prefix = os.path.join(base, *parts)
135
+
136
+ # 1. Check for a direct module file (e.g., base/parts.pyi or base/package/module.pyi)
137
+ # Example: parts=["collections", "abc"] -> candidate_prefix = base/collections/abc
138
+ # module_file_pyi = base/collections/abc.pyi
139
+ # Example: parts=["sys"] -> candidate_prefix = base/sys
140
+ # module_file_pyi = base/sys.pyi
141
+ module_file_pyi = candidate_prefix + ".pyi"
142
+ if os.path.isfile(module_file_pyi):
143
+ return module_file_pyi, "pyi"
144
+
145
+ # 2. Check if the candidate_prefix itself is a directory (package)
146
+ # and look for __init__.pyi inside it.
147
+ # Example: parts=["_typeshed"] -> candidate_prefix = base/_typeshed
148
+ # init_pyi = base/_typeshed/__init__.pyi
149
+ if os.path.isdir(candidate_prefix):
150
+ init_pyi = os.path.join(candidate_prefix, "__init__.pyi")
151
+ if os.path.isfile(init_pyi):
152
+ return init_pyi, "pyi"
153
+
154
+ # Heuristic for packages where stubs are in a subdirectory of the same name
155
+ # e.g., parts = ["requests"], candidate_prefix = base/requests
156
+ # checks base/requests/requests/__init__.pyi
157
+ # This part of the original heuristic is preserved.
158
+ if parts: # Ensure parts is not empty for parts[-1]
159
+ inner_pkg_init_pyi = os.path.join(
160
+ candidate_prefix, parts[-1], "__init__.pyi"
161
+ )
162
+ if os.path.isfile(inner_pkg_init_pyi):
163
+ return inner_pkg_init_pyi, "pyi"
164
+
165
+ return None
166
+
167
+
168
+ class PythonModuleResolver:
169
+ """Resolver for Python modules with enhanced import capabilities."""
170
+
171
+ def resolve_module_path(self, target: str, base_path: str) -> str:
172
+ """Resolve Python module path without importing."""
173
+ caller_dir = (
174
+ base_path if os.path.isdir(base_path) else os.path.dirname(base_path)
175
+ )
176
+ caller_dir = caller_dir if caller_dir else os.getcwd()
177
+ local_py_file = os.path.join(caller_dir, target.split(".")[-1] + ".py")
178
+
179
+ if os.path.exists(local_py_file):
180
+ return local_py_file
181
+ else:
182
+ raise ImportError(f"Module '{target}' not found in {caller_dir}")
@@ -0,0 +1,125 @@
1
+ """Symbol table testing helpers for Jaseci."""
2
+
3
+ from typing import Optional
4
+
5
+ from jaclang.compiler.unitree import Symbol, UniScopeNode
6
+ from jaclang.utils.test import TestCase
7
+
8
+
9
+ class SymTableTestMixin(TestCase):
10
+ """Mixin class providing assertion methods for symbol table testing."""
11
+
12
+ def assert_symbol_exists(
13
+ self,
14
+ sym_table: UniScopeNode,
15
+ symbol_name: str,
16
+ symbol_type: Optional[str] = None,
17
+ ) -> Symbol:
18
+ """Assert that a symbol exists in the symbol table."""
19
+ symbol = look_down(sym_table, symbol_name)
20
+ self.assertIsNotNone(
21
+ symbol, f"Symbol '{symbol_name}' not found in symbol table"
22
+ )
23
+ if symbol_type:
24
+ self.assertIn(
25
+ symbol_type,
26
+ str(symbol),
27
+ f"Symbol '{symbol_name}' is not of type '{symbol_type}'",
28
+ )
29
+ return symbol
30
+
31
+ def assert_symbol_decl_at(self, symbol: Symbol, line: int, col: int) -> None:
32
+ """Assert that a symbol is declared at specific line and column."""
33
+ decl_info = str(symbol)
34
+ expected_decl = f"{line}:{col}"
35
+ self.assertIn(
36
+ expected_decl,
37
+ decl_info,
38
+ f"Symbol declaration not found at {expected_decl}. Got: {decl_info}",
39
+ )
40
+
41
+ def assert_symbol_defns_at(
42
+ self, symbol: Symbol, expected_defns: list[tuple[int, int]]
43
+ ) -> None:
44
+ """Assert that a symbol has definitions at specific locations."""
45
+ symbol_str = str(symbol)
46
+ for line, col in expected_defns:
47
+ expected_defn = f"{line}:{col}"
48
+ self.assertIn(
49
+ expected_defn,
50
+ symbol_str,
51
+ f"Symbol definition not found at {expected_defn}. Got: {symbol_str}",
52
+ )
53
+
54
+ def assert_symbol_uses_at(
55
+ self, symbol: Symbol, expected_uses: list[tuple[int, int]]
56
+ ) -> None:
57
+ """Assert that a symbol has uses at specific locations."""
58
+ symbol_uses_str = str(symbol.uses)
59
+ for line, col in expected_uses:
60
+ expected_use = f"{line}:{col}"
61
+ self.assertIn(
62
+ expected_use,
63
+ symbol_uses_str,
64
+ f"Symbol use not found at {expected_use}. Got: {symbol_uses_str}",
65
+ )
66
+
67
+ def assert_symbol_complete(
68
+ self,
69
+ sym_table: UniScopeNode,
70
+ symbol_name: str,
71
+ symbol_type: str,
72
+ decl: tuple[int, int],
73
+ defns: Optional[list[tuple[int, int]]] = None,
74
+ uses: Optional[list[tuple[int, int]]] = None,
75
+ ) -> None:
76
+ """Assert complete symbol information (declaration, definitions, uses)."""
77
+ symbol = self.assert_symbol_exists(sym_table, symbol_name, symbol_type)
78
+ self.assert_symbol_decl_at(symbol, decl[0], decl[1])
79
+
80
+ if defns:
81
+ self.assert_symbol_defns_at(symbol, defns)
82
+
83
+ if uses:
84
+ self.assert_symbol_uses_at(symbol, uses)
85
+
86
+ def assert_sub_table_exists(
87
+ self, sym_table: UniScopeNode, table_name: str, tab_type: str
88
+ ) -> None:
89
+ """Assert that a sub-table exists in the symbol table."""
90
+ sub_tables = sym_table.kid_scope
91
+ table_names = [table.scope_name for table in sub_tables]
92
+ type_names = [table.get_type() for table in sub_tables]
93
+ matching_tables = [name for name in table_names if table_name in name]
94
+ matching_types = [
95
+ type_name for type_name in type_names if tab_type in str(type_name)
96
+ ]
97
+ self.assertTrue(
98
+ len(matching_tables) > 0,
99
+ f"Sub-table '{table_name}' not found. Available: {table_names}",
100
+ )
101
+ self.assertTrue(
102
+ len(matching_types) > 0,
103
+ f"Sub-table type '{tab_type}' not found in {table_names} of types {type_names}",
104
+ )
105
+ return sub_tables[table_names.index(matching_tables[0])]
106
+
107
+
108
+ def look_down(tab: UniScopeNode, name: str, deep: bool = True) -> Optional[Symbol]:
109
+ """Lookup a variable in the symbol table."""
110
+ if name in tab.names_in_scope:
111
+ if not tab.names_in_scope[name].imported:
112
+ return tab.names_in_scope[name]
113
+ else:
114
+ sym = tab.names_in_scope[name]
115
+ return sym
116
+ for i in tab.inherited_scope:
117
+ found = i.lookup(name, deep=False)
118
+ if found:
119
+ return found
120
+ if deep and tab.kid_scope:
121
+ for kid in tab.kid_scope:
122
+ found = kid.lookup(name, deep=True)
123
+ if found:
124
+ return found
125
+ return None
jaclang/utils/test.py CHANGED
@@ -9,6 +9,7 @@ from _pytest.logging import LogCaptureFixture
9
9
 
10
10
  import jaclang
11
11
  from jaclang.compiler.passes import UniPass
12
+ from jaclang.runtimelib.utils import read_file_with_encoding
12
13
  from jaclang.utils.helpers import get_uni_nodes_as_snake_case as ast_snakes
13
14
 
14
15
  import pytest
@@ -41,13 +42,11 @@ class TestCase(_TestCase):
41
42
  raise ValueError("Unable to determine the file of the module.")
42
43
  fixture_src = module.__file__
43
44
  fixture_path = os.path.join(os.path.dirname(fixture_src), "fixtures", fixture)
44
- with open(fixture_path, "r", encoding="utf-8") as f:
45
- return f.read()
45
+ return read_file_with_encoding(fixture_path)
46
46
 
47
47
  def file_to_str(self, file_path: str) -> str:
48
48
  """Load fixture from fixtures directory."""
49
- with open(file_path, "r", encoding="utf-8") as f:
50
- return f.read()
49
+ return read_file_with_encoding(file_path)
51
50
 
52
51
  def fixture_abs_path(self, fixture: str) -> str:
53
52
  """Get absolute path of a fixture from fixtures directory."""
@@ -0,0 +1,34 @@
1
+ """
2
+ A package to compare python-style regexes and test if they have intersections.
3
+ Based on the `greenery`-package by @qntm, adapted and specialized for `lark-parser`
4
+ """
5
+
6
+ from typing import Iterable, Tuple
7
+
8
+ from interegular.fsm import FSM
9
+ from interegular.patterns import Pattern, parse_pattern, REFlags, Unsupported, InvalidSyntax
10
+ from interegular.comparator import Comparator
11
+ from interegular.utils import logger
12
+
13
+ __all__ = ['FSM', 'Pattern', 'Comparator', 'parse_pattern', 'compare_patterns', 'compare_regexes', '__version__', 'REFlags', 'Unsupported',
14
+ 'InvalidSyntax']
15
+
16
+
17
+ def compare_regexes(*regexes: str) -> Iterable[Tuple[str, str]]:
18
+ """
19
+ Checks the regexes for intersections. Returns all pairs it found
20
+ """
21
+ c = Comparator({r: parse_pattern(r) for r in regexes})
22
+ print(c._patterns)
23
+ return c.check(regexes)
24
+
25
+
26
+ def compare_patterns(*ps: Pattern) -> Iterable[Tuple[Pattern, Pattern]]:
27
+ """
28
+ Checks the Patterns for intersections. Returns all pairs it found
29
+ """
30
+ c = Comparator({p: p for p in ps})
31
+ return c.check(ps)
32
+
33
+
34
+ __version__ = "0.3.3"