llparse 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {llparse-0.1.0/llparse.egg-info → llparse-0.1.2}/PKG-INFO +5 -1
- llparse-0.1.0/PKG-INFO → llparse-0.1.2/README.md +4 -10
- {llparse-0.1.0 → llparse-0.1.2}/llparse/C_compiler.py +28 -30
- {llparse-0.1.0 → llparse-0.1.2}/llparse/compilator.py +6 -6
- {llparse-0.1.0 → llparse-0.1.2}/llparse/cython_builder.py +1 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/dot.py +0 -3
- llparse-0.1.2/llparse/errors.py +2 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/frontend.py +8 -10
- {llparse-0.1.0 → llparse-0.1.2}/llparse/llparse.py +7 -2
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/builder.py +1 -1
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/loopchecker.py +80 -39
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/main_code.py +28 -10
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/parsemap.py +2 -6
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/front.py +12 -3
- llparse-0.1.2/llparse/pyfront/namespace.py +3 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/nodes.py +12 -3
- {llparse-0.1.0 → llparse-0.1.2}/llparse/settings.py +0 -1
- {llparse-0.1.0 → llparse-0.1.2}/llparse/spanalloc.py +12 -27
- {llparse-0.1.0 → llparse-0.1.2}/llparse/test.py +1 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/trie.py +0 -13
- llparse-0.1.0/README.md → llparse-0.1.2/llparse.egg-info/PKG-INFO +14 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse.egg-info/SOURCES.txt +4 -2
- {llparse-0.1.0 → llparse-0.1.2}/pyproject.toml +1 -1
- llparse-0.1.2/tests/test_loop_checker.py +160 -0
- llparse-0.1.2/tests/test_span_allocator.py +121 -0
- llparse-0.1.0/llparse/pyfront/namespace.py +0 -1
- llparse-0.1.0/llparse/tire.py +0 -158
- {llparse-0.1.0 → llparse-0.1.2}/LICENSE +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/__init__.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/constants.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/debug.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/enumerator.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/header.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/__init__.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/containers.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/implementation.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/peephole.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/transform.py +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse.egg-info/dependency_links.txt +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/llparse.egg-info/top_level.txt +0 -0
- {llparse-0.1.0 → llparse-0.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llparse
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A Parody of llparse written for writing C Parsers with Python
|
|
5
5
|
Author-email: Vizonex <VizonexBusiness@gmail.com>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -9,6 +9,10 @@ License-File: LICENSE
|
|
|
9
9
|
Dynamic: license-file
|
|
10
10
|
|
|
11
11
|
# pyllparse
|
|
12
|
+
[](https://badge.fury.io/py/llparse)
|
|
13
|
+
[](https://badge.fury.io/py/llparse)
|
|
14
|
+
[](https://opensource.org/licenses/MIT)
|
|
15
|
+
|
|
12
16
|
A python parody of the typescript library llparse.
|
|
13
17
|
|
|
14
18
|
I take no credit for the orginal work done by indutny and I was originally very nervous about making
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: llparse
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A Parody of llparse written for writing C Parsers with Python
|
|
5
|
-
Author-email: Vizonex <VizonexBusiness@gmail.com>
|
|
6
|
-
Requires-Python: >=3.9
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Dynamic: license-file
|
|
10
|
-
|
|
11
1
|
# pyllparse
|
|
2
|
+
[](https://badge.fury.io/py/llparse)
|
|
3
|
+
[](https://badge.fury.io/py/llparse)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
12
6
|
A python parody of the typescript library llparse.
|
|
13
7
|
|
|
14
8
|
I take no credit for the orginal work done by indutny and I was originally very nervous about making
|
|
@@ -29,32 +29,32 @@ class CCompiler:
|
|
|
29
29
|
out.append("#include <string.h>")
|
|
30
30
|
out.append("")
|
|
31
31
|
# Seems LLParse was updated from /* UNREACHABLE */ abort(); to a Macro, Intresting...
|
|
32
|
-
out.append(
|
|
33
|
-
out.append(
|
|
34
|
-
out.append(
|
|
35
|
-
out.append(
|
|
36
|
-
out.append(
|
|
37
|
-
out.append(
|
|
38
|
-
out.append(
|
|
39
|
-
out.append(
|
|
40
|
-
|
|
41
|
-
out.append(
|
|
42
|
-
out.append(
|
|
43
|
-
out.append(
|
|
44
|
-
out.append(
|
|
45
|
-
|
|
46
|
-
out.append(
|
|
47
|
-
out.append(
|
|
48
|
-
out.append(
|
|
49
|
-
out.append(
|
|
50
|
-
|
|
51
|
-
out.append(
|
|
52
|
-
out.append(
|
|
53
|
-
out.append(
|
|
54
|
-
out.append(
|
|
55
|
-
out.append(
|
|
56
|
-
out.append(
|
|
57
|
-
out.append(
|
|
32
|
+
out.append("#ifdef __SSE4_2__")
|
|
33
|
+
out.append(" #ifdef _MSC_VER")
|
|
34
|
+
out.append(" #include <nmmintrin.h>")
|
|
35
|
+
out.append(" #else /* !_MSC_VER */")
|
|
36
|
+
out.append(" #include <x86intrin.h>")
|
|
37
|
+
out.append(" #endif /* _MSC_VER */")
|
|
38
|
+
out.append("#endif /* __SSE4_2__ */")
|
|
39
|
+
out.append("")
|
|
40
|
+
|
|
41
|
+
out.append("#ifdef __ARM_NEON__")
|
|
42
|
+
out.append(" #include <arm_neon.h>")
|
|
43
|
+
out.append("#endif /* __ARM_NEON__ */")
|
|
44
|
+
out.append("")
|
|
45
|
+
|
|
46
|
+
out.append("#ifdef __wasm__")
|
|
47
|
+
out.append(" #include <wasm_simd128.h>")
|
|
48
|
+
out.append("#endif /* __wasm__ */")
|
|
49
|
+
out.append("")
|
|
50
|
+
|
|
51
|
+
out.append("#ifdef _MSC_VER")
|
|
52
|
+
out.append(" #define ALIGN(n) _declspec(align(n))")
|
|
53
|
+
out.append(" #define UNREACHABLE __assume(0)")
|
|
54
|
+
out.append("#else /* !_MSC_VER */")
|
|
55
|
+
out.append(" #define ALIGN(n) __attribute__((aligned(n)))")
|
|
56
|
+
out.append(" #define UNREACHABLE __builtin_unreachable()")
|
|
57
|
+
out.append("#endif /* _MSC_VER */")
|
|
58
58
|
|
|
59
59
|
out.append("")
|
|
60
60
|
out.append(
|
|
@@ -83,7 +83,7 @@ class CCompiler:
|
|
|
83
83
|
out.append("}")
|
|
84
84
|
out.append("")
|
|
85
85
|
|
|
86
|
-
# TODO (Vizonex) Make llparse_state_t's Name Optional and alterable incase mixed with
|
|
86
|
+
# TODO (Vizonex) Make llparse_state_t's Name Optional and alterable incase mixed with
|
|
87
87
|
# llhttp or another parser
|
|
88
88
|
out.append(f"static llparse_state_t {info.prefix}__run(")
|
|
89
89
|
out.append(f" {info.prefix}_t* {ARG_STATE},")
|
|
@@ -182,9 +182,7 @@ class CCompiler:
|
|
|
182
182
|
else:
|
|
183
183
|
# TODO (Vizonex) Merge lines 139 & 140 together in a future update
|
|
184
184
|
callback = (
|
|
185
|
-
f"({info.prefix}__span_cb)"
|
|
186
|
-
+ ctx.spanCbField(span.index)
|
|
187
|
-
+ f"({callback})"
|
|
185
|
+
f"(({info.prefix}__span_cb)" + ctx.spanCbField(span.index) + f")"
|
|
188
186
|
)
|
|
189
187
|
|
|
190
188
|
args = [ctx.stateArg(), posField, f"(const char*) {ctx.endPosArg()}"]
|
|
@@ -105,6 +105,9 @@ class Code(Generic[T]):
|
|
|
105
105
|
def build(self, ctx: "Compilation", out: list[str]):
|
|
106
106
|
pass
|
|
107
107
|
|
|
108
|
+
def __hash__(self):
|
|
109
|
+
return hash(self.ref)
|
|
110
|
+
|
|
108
111
|
|
|
109
112
|
class External(Code[_frontend.code.External]):
|
|
110
113
|
def build(self, ctx: "Compilation", out: list[str]):
|
|
@@ -371,7 +374,6 @@ class Consume(Node):
|
|
|
371
374
|
index = ctx.stateField(self.ref.field)
|
|
372
375
|
ty = ctx.getFieldType(self.ref.field)
|
|
373
376
|
|
|
374
|
-
|
|
375
377
|
if ty == "i64":
|
|
376
378
|
pass
|
|
377
379
|
elif ty == "i32":
|
|
@@ -880,7 +882,7 @@ class Compilation:
|
|
|
880
882
|
def buildStateEnum(self, out: list[str]):
|
|
881
883
|
# TODO (Vizonex) Give out other names that you could pass as an enum statename
|
|
882
884
|
# this is incase multiple llparse_state_e states are given to compile
|
|
883
|
-
# example would be mixing llhttp with some other source...
|
|
885
|
+
# example would be mixing llhttp with some other source...
|
|
884
886
|
out.append("enum llparse_state_e {")
|
|
885
887
|
out.append(f" {STATE_ERROR},")
|
|
886
888
|
for stateName in self.stateDict.keys():
|
|
@@ -988,8 +990,7 @@ class Compilation:
|
|
|
988
990
|
out.append(f"{LABEL_PREFIX}{name} : " + "{")
|
|
989
991
|
for line in lines:
|
|
990
992
|
out.append(f" {line}")
|
|
991
|
-
out.append("
|
|
992
|
-
out.append(" abort();")
|
|
993
|
+
out.append(" UNREACHABLE;")
|
|
993
994
|
out.append("}")
|
|
994
995
|
|
|
995
996
|
def buildInternalStates(self, out: list[str]):
|
|
@@ -1000,8 +1001,7 @@ class Compilation:
|
|
|
1000
1001
|
out.append(f"{LABEL_PREFIX}{name}: " + "{")
|
|
1001
1002
|
for line in lines:
|
|
1002
1003
|
out.append(f" {line}")
|
|
1003
|
-
out.append("
|
|
1004
|
-
out.append(" abort();")
|
|
1004
|
+
out.append(" UNREACHABLE;")
|
|
1005
1005
|
out.append("}")
|
|
1006
1006
|
|
|
1007
1007
|
def addState(self, state: str, lines: list[str]):
|
|
@@ -4,6 +4,7 @@ from typing import Literal, Optional, Union
|
|
|
4
4
|
from .enumerator import Enumerator
|
|
5
5
|
from .pybuilder import LoopChecker
|
|
6
6
|
from .pybuilder import builder as source
|
|
7
|
+
|
|
7
8
|
# from pyfront.namespace import code, node , transform
|
|
8
9
|
from .pyfront import namespace as _frontend
|
|
9
10
|
from .pyfront.front import Identifier, IWrap, SpanField
|
|
@@ -17,13 +18,10 @@ DEFAULT_MIN_TABLE_SIZE = 32
|
|
|
17
18
|
DEFAULT_MAX_TABLE_WIDTH = 4
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
|
|
21
21
|
WrappedNode = IWrap[_frontend.node.Node]
|
|
22
22
|
WrappedCode = IWrap[_frontend.code.Code]
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
25
|
@dataclass
|
|
28
26
|
class ITableLookupTarget:
|
|
29
27
|
trie: TrieEmpty
|
|
@@ -309,18 +307,18 @@ class Frontend:
|
|
|
309
307
|
bailout = False
|
|
310
308
|
for child in trie.children:
|
|
311
309
|
if isinstance(child.node, TrieEmpty):
|
|
312
|
-
print(
|
|
313
|
-
|
|
314
|
-
)
|
|
310
|
+
# print(
|
|
311
|
+
# 'non-leaf trie child of "%s" prevents table allocation' % node.name
|
|
312
|
+
# )
|
|
315
313
|
bailout = False
|
|
316
314
|
continue
|
|
317
315
|
|
|
318
316
|
empty: TrieEmpty = child.node
|
|
319
317
|
if getattr(empty, "value", None) is None:
|
|
320
|
-
print(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
)
|
|
318
|
+
# print(
|
|
319
|
+
# 'value passing trie leaf of "%s" prevents table allocation'
|
|
320
|
+
# % node.name
|
|
321
|
+
# )
|
|
324
322
|
bailout = False
|
|
325
323
|
continue
|
|
326
324
|
|
|
@@ -4,8 +4,13 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from .C_compiler import CCompiler
|
|
7
|
-
from .frontend import (
|
|
8
|
-
|
|
7
|
+
from .frontend import (
|
|
8
|
+
DEFAULT_MAX_TABLE_WIDTH,
|
|
9
|
+
DEFAULT_MIN_TABLE_SIZE,
|
|
10
|
+
Frontend,
|
|
11
|
+
IImplementation,
|
|
12
|
+
source,
|
|
13
|
+
)
|
|
9
14
|
from .header import HeaderBuilder
|
|
10
15
|
|
|
11
16
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from typing import Any, Literal, Union
|
|
2
3
|
|
|
4
|
+
from ..errors import Error
|
|
3
5
|
from ..pybuilder.main_code import Node
|
|
4
6
|
|
|
7
|
+
logger = logging.getLogger("llparse.pybuilder.loopchecker")
|
|
8
|
+
logger.setLevel(logging.INFO)
|
|
9
|
+
|
|
10
|
+
|
|
5
11
|
MAX_VALUE = 256
|
|
6
12
|
WORD_SIZE = 32
|
|
7
13
|
SIZE = MAX_VALUE // WORD_SIZE
|
|
@@ -19,17 +25,20 @@ assert MAX_VALUE % WORD_SIZE == 0
|
|
|
19
25
|
|
|
20
26
|
class Lattice:
|
|
21
27
|
def __init__(
|
|
22
|
-
self, value: Union[Any, list[int], Literal["empty"], Literal["any"]]
|
|
28
|
+
self, value: Union[Any, list[int], bytes, Literal["empty"], Literal["any"]]
|
|
23
29
|
) -> None:
|
|
24
30
|
self.value = value
|
|
25
31
|
self.words: list[int] = []
|
|
26
32
|
|
|
27
33
|
# allocate space by filling in data with zeros...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
if value != "any":
|
|
35
|
+
for _ in range(SIZE):
|
|
36
|
+
self.words.append(0)
|
|
37
|
+
else:
|
|
38
|
+
for _ in range(SIZE):
|
|
39
|
+
self.words.append(WORD_FILL)
|
|
40
|
+
|
|
41
|
+
if isinstance(value, (list, bytes)):
|
|
33
42
|
for single in value:
|
|
34
43
|
self.add(single)
|
|
35
44
|
|
|
@@ -69,6 +78,9 @@ class Lattice:
|
|
|
69
78
|
result.words[i] = self.words[i] & other.words[i]
|
|
70
79
|
return result
|
|
71
80
|
|
|
81
|
+
def __repr__(self):
|
|
82
|
+
return f"<Lattice {', '.join(f'{k}: {v!r}' for k, v in self.__dict__.items())}>"
|
|
83
|
+
|
|
72
84
|
def subtract(self, other: "Lattice") -> "Lattice":
|
|
73
85
|
result = Lattice("empty")
|
|
74
86
|
for i in range(SIZE):
|
|
@@ -76,7 +88,13 @@ class Lattice:
|
|
|
76
88
|
return result
|
|
77
89
|
|
|
78
90
|
def isEqual(self, other: "Lattice"):
|
|
79
|
-
|
|
91
|
+
if self.toJSON() == other.toJSON():
|
|
92
|
+
return True
|
|
93
|
+
else:
|
|
94
|
+
for i in range(SIZE):
|
|
95
|
+
if self.words[i] != other.words[i]:
|
|
96
|
+
return False
|
|
97
|
+
return True
|
|
80
98
|
|
|
81
99
|
def toJSON(self):
|
|
82
100
|
isEmpty = True
|
|
@@ -111,7 +129,9 @@ class Reachability:
|
|
|
111
129
|
if otherwise:
|
|
112
130
|
queue.append(otherwise.node)
|
|
113
131
|
|
|
114
|
-
|
|
132
|
+
# Reverse the order so that we always
|
|
133
|
+
# throw an error on bad configurations...
|
|
134
|
+
return res
|
|
115
135
|
|
|
116
136
|
|
|
117
137
|
EMPTY_VALUE = Lattice("empty")
|
|
@@ -124,38 +144,45 @@ class LoopChecker:
|
|
|
124
144
|
self.terminatedCache: dict[Node, Lattice] = {}
|
|
125
145
|
|
|
126
146
|
def clear(self, nodes: list[Node]):
|
|
127
|
-
|
|
147
|
+
for node in nodes:
|
|
148
|
+
self.lattice[node] = EMPTY_VALUE
|
|
128
149
|
|
|
129
150
|
def check(self, root: Node):
|
|
130
151
|
r = Reachability()
|
|
131
152
|
nodes = r.build(root)
|
|
153
|
+
|
|
132
154
|
for node in nodes:
|
|
133
155
|
self.clear(nodes)
|
|
156
|
+
logger.debug("checking loops starting from %s" % node.name)
|
|
134
157
|
self.lattice[node] = ANY_VALUE
|
|
158
|
+
# we must eliminate randomness so that error always throw
|
|
135
159
|
changed: set[Node] = set([root])
|
|
136
|
-
while len(changed) != 0:
|
|
137
|
-
next = set()
|
|
138
|
-
for changedNode in next:
|
|
139
|
-
self.propagate(changedNode, next)
|
|
140
|
-
changed = next
|
|
141
160
|
|
|
142
|
-
|
|
161
|
+
while changed:
|
|
162
|
+
logger.debug("changed %s" % [n.name for n in changed])
|
|
163
|
+
_next = set()
|
|
164
|
+
for changedNode in changed:
|
|
165
|
+
self.propagate(changedNode, _next)
|
|
166
|
+
changed = _next
|
|
167
|
+
logger.debug("lattice stabilized")
|
|
168
|
+
self.visit(root, [])
|
|
143
169
|
|
|
144
170
|
def propagate(self, node: Node, changed: set[Node]):
|
|
145
171
|
value = self.lattice[node]
|
|
146
|
-
terminated
|
|
147
|
-
|
|
172
|
+
terminated = self.terminate(node)
|
|
173
|
+
logger.debug("propagate(%r), initial value %r" % (node.name, value.toJSON()))
|
|
174
|
+
if not terminated.isEqual(EMPTY_VALUE):
|
|
175
|
+
logger.debug("node %s terminates %r" % (node.name, terminated.toJSON()))
|
|
148
176
|
value = value.subtract(terminated)
|
|
149
177
|
if value.isEqual(EMPTY_VALUE):
|
|
150
178
|
return
|
|
151
179
|
|
|
152
|
-
keysbyTarget: dict[Node, Lattice] =
|
|
180
|
+
keysbyTarget: dict[Node, Lattice] = {}
|
|
153
181
|
|
|
154
182
|
for edge in node.getAllEdges():
|
|
155
183
|
if not edge.noAdvance:
|
|
156
184
|
continue
|
|
157
185
|
|
|
158
|
-
targetValue: Lattice
|
|
159
186
|
if keysbyTarget.get(edge.node):
|
|
160
187
|
targetValue = keysbyTarget[edge.node]
|
|
161
188
|
else:
|
|
@@ -170,9 +197,14 @@ class LoopChecker:
|
|
|
170
197
|
continue
|
|
171
198
|
|
|
172
199
|
targetValue = targetValue.union(edgeValue)
|
|
200
|
+
|
|
173
201
|
keysbyTarget[edge.node] = targetValue
|
|
174
202
|
|
|
175
203
|
for child, childValue in keysbyTarget.items():
|
|
204
|
+
logger.debug(
|
|
205
|
+
"node %r propagates %r to %r"
|
|
206
|
+
% (node.name, childValue.toJSON(), child.name)
|
|
207
|
+
)
|
|
176
208
|
self.update(child, childValue, changed)
|
|
177
209
|
# FINISHED!
|
|
178
210
|
|
|
@@ -182,13 +214,13 @@ class LoopChecker:
|
|
|
182
214
|
return False
|
|
183
215
|
self.lattice[node] = newValue
|
|
184
216
|
changed.add(node)
|
|
217
|
+
return True
|
|
185
218
|
|
|
186
|
-
def terminate(self, node: Node
|
|
187
|
-
if self.terminatedCache
|
|
219
|
+
def terminate(self, node: Node):
|
|
220
|
+
if node in self.terminatedCache:
|
|
188
221
|
return self.terminatedCache[node]
|
|
189
222
|
|
|
190
223
|
terminated: list[int] = []
|
|
191
|
-
|
|
192
224
|
for edge in node.getAllEdges():
|
|
193
225
|
if edge.noAdvance:
|
|
194
226
|
continue
|
|
@@ -204,43 +236,52 @@ class LoopChecker:
|
|
|
204
236
|
|
|
205
237
|
def visit(self, node: Node, path: list[Node]):
|
|
206
238
|
value = self.lattice[node]
|
|
239
|
+
logger.debug("enter %s, value is %s" % (node.name, value.toJSON()))
|
|
240
|
+
|
|
207
241
|
terminated = (
|
|
208
|
-
|
|
209
|
-
if self.terminatedCache
|
|
210
|
-
else
|
|
242
|
+
EMPTY_VALUE
|
|
243
|
+
if node not in self.terminatedCache
|
|
244
|
+
else self.terminatedCache[node]
|
|
211
245
|
)
|
|
212
|
-
|
|
246
|
+
|
|
247
|
+
if not terminated.isEqual(EMPTY_VALUE):
|
|
248
|
+
logger.debug(f"subtract terminated {terminated}")
|
|
213
249
|
value = value.subtract(terminated)
|
|
214
250
|
if value.isEqual(EMPTY_VALUE):
|
|
251
|
+
logger.debug("terminated everything")
|
|
215
252
|
return
|
|
216
253
|
|
|
217
254
|
for edge in node.getAllEdges():
|
|
218
|
-
if edge.noAdvance:
|
|
255
|
+
if not edge.noAdvance:
|
|
219
256
|
continue
|
|
220
257
|
edgeValue = value
|
|
221
|
-
if
|
|
258
|
+
if edge.key is None or isinstance(edge.key, int):
|
|
259
|
+
pass
|
|
260
|
+
else:
|
|
222
261
|
edgeValue = edgeValue.intersect(Lattice([edge.key[0]]))
|
|
223
262
|
|
|
224
263
|
if edgeValue.isEqual(EMPTY_VALUE):
|
|
264
|
+
# logger.debug(edge.node.name + " not recursive")
|
|
225
265
|
continue
|
|
226
266
|
|
|
227
|
-
def indexOf(path: list, obj):
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
267
|
+
def indexOf(path: list[Node], obj: Node) -> int:
|
|
268
|
+
for o in path:
|
|
269
|
+
if o.name == obj.name:
|
|
270
|
+
return 0
|
|
271
|
+
return -1
|
|
232
272
|
|
|
233
|
-
if indexOf(path, node) != -1:
|
|
234
|
-
if len(path) ==
|
|
235
|
-
raise
|
|
236
|
-
f'Detected
|
|
273
|
+
if indexOf(path, edge.node) != -1:
|
|
274
|
+
if len(path) == 1:
|
|
275
|
+
raise Error(
|
|
276
|
+
f'Detected loop in "{edge.node.name}" through "{edge.node.name}"'
|
|
237
277
|
)
|
|
238
278
|
|
|
239
|
-
raise
|
|
279
|
+
raise Error(
|
|
240
280
|
'Detected loop in "'
|
|
241
281
|
+ edge.node.name
|
|
242
282
|
+ '" through chain '
|
|
243
283
|
+ (" -> ").join(['"' + name.name + '"' for name in path])
|
|
244
284
|
)
|
|
245
285
|
|
|
246
|
-
self.visit(edge.node, path
|
|
286
|
+
self.visit(edge.node, path + [edge.node])
|
|
287
|
+
logger.debug("leave %s" % node.name)
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import sys
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Optional, TypeVar, Union
|
|
4
|
+
from typing import Callable, Literal, Optional, TypeVar, Union
|
|
5
|
+
|
|
6
|
+
if sys.version_info < (3, 10):
|
|
7
|
+
from typing_extensions import ParamSpec
|
|
8
|
+
else:
|
|
9
|
+
from typing import ParamSpec
|
|
10
|
+
|
|
11
|
+
_P = ParamSpec("_P")
|
|
12
|
+
_T = TypeVar("_T")
|
|
13
|
+
|
|
4
14
|
|
|
5
15
|
Signature = ["match", "value"]
|
|
6
16
|
|
|
@@ -18,7 +28,7 @@ def toBuffer(value: Union[str, int]):
|
|
|
18
28
|
# TODO Add text validataion...
|
|
19
29
|
|
|
20
30
|
|
|
21
|
-
def validate_text(init):
|
|
31
|
+
def validate_text(init: Callable[_P, _T]) -> Callable[_P, _T]:
|
|
22
32
|
def is_valid(args, kwargs):
|
|
23
33
|
if kwargs.get("field"):
|
|
24
34
|
field = kwargs["field"]
|
|
@@ -32,15 +42,20 @@ def validate_text(init):
|
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
class Code:
|
|
35
|
-
def __init__(self, signature:
|
|
36
|
-
assert signature in Signature
|
|
45
|
+
def __init__(self, signature: Literal["match", "value"], name: str) -> None:
|
|
46
|
+
assert signature in Signature, "Invalid signature %s" % signature
|
|
37
47
|
|
|
38
48
|
self.signature = signature
|
|
39
49
|
self.name = name
|
|
40
50
|
|
|
51
|
+
def __hash__(self):
|
|
52
|
+
return hash(self.signature + self.name)
|
|
53
|
+
|
|
41
54
|
|
|
42
55
|
class Field(Code):
|
|
43
|
-
def __init__(
|
|
56
|
+
def __init__(
|
|
57
|
+
self, signature: Literal["match", "value"], name: str, field: str
|
|
58
|
+
) -> None:
|
|
44
59
|
self.field = field
|
|
45
60
|
# if re.search(r"[//\s\\]+",field):
|
|
46
61
|
# raise TypeError(f"Can\'t access internal field from user code because the field: {name} conatins invalid characters")
|
|
@@ -48,11 +63,10 @@ class Field(Code):
|
|
|
48
63
|
|
|
49
64
|
|
|
50
65
|
class FieldValue(Field):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
def __init__(
|
|
67
|
+
self, signature: Literal["match", "value"], name: str, field: str, value: int
|
|
68
|
+
) -> None:
|
|
54
69
|
self.value = value
|
|
55
|
-
self.field = field
|
|
56
70
|
super().__init__(signature, name, field)
|
|
57
71
|
|
|
58
72
|
|
|
@@ -131,6 +145,10 @@ class Node:
|
|
|
131
145
|
self.otherwiseEdge: Optional["Edge"] = None
|
|
132
146
|
self.privEdges: list["Edge"] = []
|
|
133
147
|
|
|
148
|
+
def key(self):
|
|
149
|
+
"""reversed for sorting to prevent python from creating artificial randomness"""
|
|
150
|
+
return self.name
|
|
151
|
+
|
|
134
152
|
def __hash__(self) -> int:
|
|
135
153
|
return hash(self.name)
|
|
136
154
|
|
|
@@ -288,7 +306,7 @@ class Edge:
|
|
|
288
306
|
self.node = node
|
|
289
307
|
self.noAdvance = noAdvance
|
|
290
308
|
|
|
291
|
-
self.key = key
|
|
309
|
+
self.key = key.encode() if isinstance(key, str) else key
|
|
292
310
|
self.value = value
|
|
293
311
|
|
|
294
312
|
# Validation...
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
from main_code import Edge, Node
|
|
1
|
+
from ..pybuilder.main_code import Edge, Node
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
class ParserMap:
|
|
@@ -9,9 +8,7 @@ class ParserMap:
|
|
|
9
8
|
def Jsonize(self):
|
|
10
9
|
queue = [self.root]
|
|
11
10
|
seen: set[Node] = set()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
while len(queue) != 0:
|
|
11
|
+
while queue:
|
|
15
12
|
node = queue.pop()
|
|
16
13
|
|
|
17
14
|
if node in seen:
|
|
@@ -33,5 +30,4 @@ class ParserMap:
|
|
|
33
30
|
return {}
|
|
34
31
|
data = edge.__dict__
|
|
35
32
|
data["node"] = edge.node
|
|
36
|
-
# print(data["node"].name)
|
|
37
33
|
return data
|
|
@@ -10,12 +10,12 @@ T = TypeVar("T")
|
|
|
10
10
|
Signature = TypeVar("Signature", bytes, str)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@dataclass
|
|
13
|
+
@dataclass(unsafe_hash=True)
|
|
14
14
|
class IWrap(Generic[T]):
|
|
15
15
|
ref: T
|
|
16
16
|
|
|
17
|
-
def __hash__(self) -> int:
|
|
18
|
-
|
|
17
|
+
# def __hash__(self) -> int:
|
|
18
|
+
# return hash(self.ref)
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def toCacheKey(value: Union[int, bool]) -> str:
|
|
@@ -33,6 +33,9 @@ class Code:
|
|
|
33
33
|
cacheKey: str
|
|
34
34
|
name: str
|
|
35
35
|
|
|
36
|
+
def __hash__(self):
|
|
37
|
+
return hash(self.cacheKey)
|
|
38
|
+
|
|
36
39
|
|
|
37
40
|
class External(Code):
|
|
38
41
|
"""Inherits from the `Code` class as a subclass of `Code`"""
|
|
@@ -47,6 +50,9 @@ class Field(Code):
|
|
|
47
50
|
|
|
48
51
|
field: str
|
|
49
52
|
|
|
53
|
+
def __hash__(self):
|
|
54
|
+
return hash(self.cacheKey)
|
|
55
|
+
|
|
50
56
|
|
|
51
57
|
class FieldValueError(Exception):
|
|
52
58
|
"""FieldValue `value` must be integer"""
|
|
@@ -167,6 +173,9 @@ class IUniqueName:
|
|
|
167
173
|
name: str
|
|
168
174
|
originalName: str
|
|
169
175
|
|
|
176
|
+
def __hash__(self):
|
|
177
|
+
return hash(self.originalName)
|
|
178
|
+
|
|
170
179
|
|
|
171
180
|
@dataclass
|
|
172
181
|
class Identifier:
|