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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from itertools import combinations
|
|
4
|
+
from typing import List, Tuple, Any, Dict, Iterable, Set, FrozenSet, Optional
|
|
5
|
+
|
|
6
|
+
from interegular import InvalidSyntax, REFlags
|
|
7
|
+
from interegular.fsm import FSM, Alphabet, anything_else
|
|
8
|
+
from interegular.patterns import Pattern, Unsupported, parse_pattern
|
|
9
|
+
from interegular.utils import logger, soft_repr
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ExampleCollision:
|
|
14
|
+
"""
|
|
15
|
+
Captures the full text of an example collision between two regex.
|
|
16
|
+
`main_text` is the part that actually gets captured by the two regex
|
|
17
|
+
`prefix` is the part that is potentially needed for lookbehinds
|
|
18
|
+
`postfix` is the part that is potentially needed for lookahead
|
|
19
|
+
"""
|
|
20
|
+
prefix: str
|
|
21
|
+
main_text: str
|
|
22
|
+
postfix: str
|
|
23
|
+
|
|
24
|
+
def format_multiline(self, intro: str = "Example Collision: ", indent: str = "",
|
|
25
|
+
force_pointer: bool = False) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Formats this example somewhat similar to a python syntax error.
|
|
28
|
+
- intro is added on the first line
|
|
29
|
+
- indent is added on the second line
|
|
30
|
+
The three parts of the example are concatenated and `^` is used to underline them.
|
|
31
|
+
|
|
32
|
+
ExampleCollision(prefix='a', main_text='cd', postfix='ef').format_multiline()
|
|
33
|
+
|
|
34
|
+
leads to
|
|
35
|
+
|
|
36
|
+
Example Collision: acdef
|
|
37
|
+
^^
|
|
38
|
+
|
|
39
|
+
This function will escape the character where necessary to stay readable.
|
|
40
|
+
if `force_pointer` is False, the function will not produce the second line if only main_text is set
|
|
41
|
+
"""
|
|
42
|
+
if len(intro) < len(indent):
|
|
43
|
+
raise ValueError("Can't have intro be shorter than indent")
|
|
44
|
+
prefix = soft_repr(self.prefix)
|
|
45
|
+
main_text = soft_repr(self.main_text)
|
|
46
|
+
postfix = soft_repr(self.postfix)
|
|
47
|
+
text = f"{prefix}{main_text}{postfix}"
|
|
48
|
+
if len(text) != len(main_text):
|
|
49
|
+
whitespace = ' ' * (len(intro) - len(indent) + len(prefix))
|
|
50
|
+
pointers = '^' * len(main_text)
|
|
51
|
+
return f"{intro}{text}\n{indent}{whitespace}{pointers}"
|
|
52
|
+
else:
|
|
53
|
+
return f"{intro}{text}"
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def full_text(self):
|
|
57
|
+
return self.prefix + self.main_text + self.postfix
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Comparator:
|
|
61
|
+
"""
|
|
62
|
+
A class that represents the main interface for comparing a list of regex to each other.
|
|
63
|
+
It expects a dictionary of arbitrary labels mapped to `Pattern` instances,
|
|
64
|
+
but there is a utility function to create the instances `from_regex` strings.
|
|
65
|
+
|
|
66
|
+
The main interface function all expect the abitrary labels to be given, which
|
|
67
|
+
then get mapped to the correct `Pattern` and/or `FSM` instance.
|
|
68
|
+
|
|
69
|
+
There is a utility function `mark(a,b)` which allows to mark pairs that shouldn't
|
|
70
|
+
be checked again by `check`.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, patterns: Dict[Any, Pattern]):
|
|
74
|
+
self._patterns = patterns
|
|
75
|
+
self._marked_pairs: Set[FrozenSet[Any]] = set()
|
|
76
|
+
if not patterns: # `isdisjoint` can not be called anyway, so we don't need to create a valid state
|
|
77
|
+
return
|
|
78
|
+
self._alphabet = Alphabet.union(*(p.get_alphabet(REFlags(0)) for p in patterns.values()))[0]
|
|
79
|
+
prefix_postfix_s = [p.prefix_postfix for p in patterns.values()]
|
|
80
|
+
self._prefix_postfix = max(p[0] for p in prefix_postfix_s), max(p[1] for p in prefix_postfix_s)
|
|
81
|
+
self._fsms: Dict[Any, FSM] = {}
|
|
82
|
+
self._know_pairs: Dict[Tuple[Any, Any], bool] = {}
|
|
83
|
+
|
|
84
|
+
def get_fsm(self, a: Any) -> FSM:
|
|
85
|
+
if a not in self._fsms:
|
|
86
|
+
try:
|
|
87
|
+
self._fsms[a] = self._patterns[a].to_fsm(self._alphabet, self._prefix_postfix)
|
|
88
|
+
except Unsupported as e:
|
|
89
|
+
self._fsms[a] = None
|
|
90
|
+
logger.warning(f"Can't compile Pattern to fsm for {a}\n {repr(e)}")
|
|
91
|
+
except KeyError:
|
|
92
|
+
self._fsms[a] = None # In case it was thrown away in `from_regexes`
|
|
93
|
+
return self._fsms[a]
|
|
94
|
+
|
|
95
|
+
def isdisjoint(self, a: Any, b: Any) -> bool:
|
|
96
|
+
if (a, b) not in self._know_pairs:
|
|
97
|
+
fa, fb = self.get_fsm(a), self.get_fsm(b)
|
|
98
|
+
if fa is None or fb is None:
|
|
99
|
+
self._know_pairs[a, b] = True # We can't know. Assume they are disjoint
|
|
100
|
+
else:
|
|
101
|
+
self._know_pairs[a, b] = fa.isdisjoint(fb)
|
|
102
|
+
return self._know_pairs[a, b]
|
|
103
|
+
|
|
104
|
+
def check(self, keys: Iterable[Any] = None, skip_marked: bool = False) -> Iterable[Tuple[Any, Any]]:
|
|
105
|
+
if keys is None:
|
|
106
|
+
keys = self._patterns
|
|
107
|
+
for a, b in combinations(keys, 2):
|
|
108
|
+
if skip_marked and self.is_marked(a, b):
|
|
109
|
+
continue
|
|
110
|
+
if not self.isdisjoint(a, b):
|
|
111
|
+
yield a, b
|
|
112
|
+
|
|
113
|
+
def get_example_overlap(self, a: Any, b: Any, max_time: float = None) -> ExampleCollision:
|
|
114
|
+
pa, pb = self._patterns[a], self._patterns[b]
|
|
115
|
+
needed_pre = max(pa.prefix_postfix[0], pb.prefix_postfix[0])
|
|
116
|
+
needed_post = max(pa.prefix_postfix[1], pb.prefix_postfix[1])
|
|
117
|
+
|
|
118
|
+
# We use the optimal alphabet here instead of the general one since that
|
|
119
|
+
# massively improves performance by every metric.
|
|
120
|
+
alphabet = pa.get_alphabet(REFlags(0)).union(pb.get_alphabet(REFlags(0)))[0]
|
|
121
|
+
fa, fb = pa.to_fsm(alphabet, (needed_pre, needed_post)), pb.to_fsm(alphabet, (needed_pre, needed_post))
|
|
122
|
+
intersection = fa.intersection(fb)
|
|
123
|
+
if max_time is None:
|
|
124
|
+
max_iterations = None
|
|
125
|
+
else:
|
|
126
|
+
# We calculate an approximation for that value of max_iterations
|
|
127
|
+
# that makes sure for this function to finish in under max_time seconds
|
|
128
|
+
# This values will heavily depend on CPU, python version, exact patterns
|
|
129
|
+
# and probably more factors, but this should generally be in the correct
|
|
130
|
+
# ballpark.
|
|
131
|
+
max_iterations = int((max_time - 0.09)/(1.4e-6 * len(alphabet)))
|
|
132
|
+
try:
|
|
133
|
+
text = next(intersection.strings(max_iterations))
|
|
134
|
+
except StopIteration:
|
|
135
|
+
raise ValueError(f"No overlap between {a} and {b} exists")
|
|
136
|
+
text = ''.join(c if c != anything_else else '?' for c in text)
|
|
137
|
+
if needed_post > 0:
|
|
138
|
+
return ExampleCollision(text[:needed_pre], text[needed_pre:-needed_post], text[-needed_post:])
|
|
139
|
+
else:
|
|
140
|
+
return ExampleCollision(text[:needed_pre], text[needed_pre:], '')
|
|
141
|
+
|
|
142
|
+
def is_marked(self, a: Any, b: Any) -> bool:
|
|
143
|
+
return frozenset({a, b}) in self._marked_pairs
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def marked_pairs(self):
|
|
147
|
+
return self._marked_pairs
|
|
148
|
+
|
|
149
|
+
def count_marked_pairs(self):
|
|
150
|
+
return len(self._marked_pairs)
|
|
151
|
+
|
|
152
|
+
def mark(self, a: Any, b: Any):
|
|
153
|
+
self._marked_pairs.add(frozenset({a, b}))
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def from_regexes(cls, regexes: Dict[Any, str]):
|
|
157
|
+
patterns = {}
|
|
158
|
+
for k, r in regexes.items():
|
|
159
|
+
try:
|
|
160
|
+
patterns[k] = parse_pattern(r)
|
|
161
|
+
except (Unsupported, InvalidSyntax) as e:
|
|
162
|
+
logger.warning(f"Can't compile regex to Pattern for {k}\n {repr(e)}")
|
|
163
|
+
return cls(patterns)
|