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.
- jaclang/cli/cli.py +74 -22
- jaclang/compiler/jac.lark +3 -3
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +14 -21
- jaclang/compiler/passes/main/__init__.py +3 -1
- jaclang/compiler/passes/main/binder_pass.py +594 -0
- jaclang/compiler/passes/main/import_pass.py +8 -256
- jaclang/compiler/passes/main/inheritance_pass.py +2 -2
- jaclang/compiler/passes/main/pyast_gen_pass.py +35 -69
- jaclang/compiler/passes/main/pyast_load_pass.py +24 -13
- jaclang/compiler/passes/main/sem_def_match_pass.py +1 -1
- jaclang/compiler/passes/main/tests/fixtures/M1.jac +3 -0
- jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +47 -0
- jaclang/compiler/passes/main/tests/test_binder_pass.py +111 -0
- jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +13 -13
- jaclang/compiler/passes/main/tests/test_sem_def_match_pass.py +6 -6
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +2 -0
- jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +6 -0
- jaclang/compiler/program.py +15 -8
- jaclang/compiler/tests/test_sr_errors.py +32 -0
- jaclang/compiler/unitree.py +21 -15
- jaclang/langserve/engine.jac +23 -4
- jaclang/langserve/tests/test_server.py +13 -0
- jaclang/runtimelib/importer.py +33 -62
- jaclang/runtimelib/utils.py +29 -0
- jaclang/tests/fixtures/pyfunc_fmt.py +60 -0
- jaclang/tests/fixtures/pyfunc_fstr.py +25 -0
- jaclang/tests/fixtures/pyfunc_kwesc.py +33 -0
- jaclang/tests/fixtures/python_run_test.py +19 -0
- jaclang/tests/test_cli.py +67 -0
- jaclang/tests/test_language.py +96 -1
- jaclang/utils/lang_tools.py +3 -3
- jaclang/utils/module_resolver.py +90 -0
- jaclang/utils/symtable_test_helpers.py +125 -0
- jaclang/utils/test.py +3 -4
- jaclang/vendor/interegular/__init__.py +34 -0
- jaclang/vendor/interegular/comparator.py +163 -0
- jaclang/vendor/interegular/fsm.py +1015 -0
- jaclang/vendor/interegular/patterns.py +732 -0
- jaclang/vendor/interegular/py.typed +0 -0
- jaclang/vendor/interegular/utils/__init__.py +15 -0
- jaclang/vendor/interegular/utils/simple_parser.py +165 -0
- jaclang/vendor/interegular-0.3.3.dist-info/INSTALLER +1 -0
- jaclang/vendor/interegular-0.3.3.dist-info/LICENSE.txt +21 -0
- jaclang/vendor/interegular-0.3.3.dist-info/METADATA +64 -0
- jaclang/vendor/interegular-0.3.3.dist-info/RECORD +20 -0
- jaclang/vendor/interegular-0.3.3.dist-info/REQUESTED +0 -0
- jaclang/vendor/interegular-0.3.3.dist-info/WHEEL +5 -0
- jaclang/vendor/interegular-0.3.3.dist-info/top_level.txt +1 -0
- {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/METADATA +1 -1
- {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/RECORD +53 -29
- {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/WHEEL +0 -0
- {jaclang-0.8.4.dist-info → jaclang-0.8.5.dist-info}/entry_points.txt +0 -0
jaclang/runtimelib/utils.py
CHANGED
|
@@ -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()
|
jaclang/tests/test_language.py
CHANGED
|
@@ -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)
|
jaclang/utils/lang_tools.py
CHANGED
|
@@ -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
|
-
|
|
198
|
-
|
|
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:
|
jaclang/utils/module_resolver.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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"
|