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.
Files changed (41) hide show
  1. {llparse-0.1.0/llparse.egg-info → llparse-0.1.2}/PKG-INFO +5 -1
  2. llparse-0.1.0/PKG-INFO → llparse-0.1.2/README.md +4 -10
  3. {llparse-0.1.0 → llparse-0.1.2}/llparse/C_compiler.py +28 -30
  4. {llparse-0.1.0 → llparse-0.1.2}/llparse/compilator.py +6 -6
  5. {llparse-0.1.0 → llparse-0.1.2}/llparse/cython_builder.py +1 -0
  6. {llparse-0.1.0 → llparse-0.1.2}/llparse/dot.py +0 -3
  7. llparse-0.1.2/llparse/errors.py +2 -0
  8. {llparse-0.1.0 → llparse-0.1.2}/llparse/frontend.py +8 -10
  9. {llparse-0.1.0 → llparse-0.1.2}/llparse/llparse.py +7 -2
  10. {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/builder.py +1 -1
  11. {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/loopchecker.py +80 -39
  12. {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/main_code.py +28 -10
  13. {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/parsemap.py +2 -6
  14. {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/front.py +12 -3
  15. llparse-0.1.2/llparse/pyfront/namespace.py +3 -0
  16. {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/nodes.py +12 -3
  17. {llparse-0.1.0 → llparse-0.1.2}/llparse/settings.py +0 -1
  18. {llparse-0.1.0 → llparse-0.1.2}/llparse/spanalloc.py +12 -27
  19. {llparse-0.1.0 → llparse-0.1.2}/llparse/test.py +1 -0
  20. {llparse-0.1.0 → llparse-0.1.2}/llparse/trie.py +0 -13
  21. llparse-0.1.0/README.md → llparse-0.1.2/llparse.egg-info/PKG-INFO +14 -0
  22. {llparse-0.1.0 → llparse-0.1.2}/llparse.egg-info/SOURCES.txt +4 -2
  23. {llparse-0.1.0 → llparse-0.1.2}/pyproject.toml +1 -1
  24. llparse-0.1.2/tests/test_loop_checker.py +160 -0
  25. llparse-0.1.2/tests/test_span_allocator.py +121 -0
  26. llparse-0.1.0/llparse/pyfront/namespace.py +0 -1
  27. llparse-0.1.0/llparse/tire.py +0 -158
  28. {llparse-0.1.0 → llparse-0.1.2}/LICENSE +0 -0
  29. {llparse-0.1.0 → llparse-0.1.2}/llparse/__init__.py +0 -0
  30. {llparse-0.1.0 → llparse-0.1.2}/llparse/constants.py +0 -0
  31. {llparse-0.1.0 → llparse-0.1.2}/llparse/debug.py +0 -0
  32. {llparse-0.1.0 → llparse-0.1.2}/llparse/enumerator.py +0 -0
  33. {llparse-0.1.0 → llparse-0.1.2}/llparse/header.py +0 -0
  34. {llparse-0.1.0 → llparse-0.1.2}/llparse/pybuilder/__init__.py +0 -0
  35. {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/containers.py +0 -0
  36. {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/implementation.py +0 -0
  37. {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/peephole.py +0 -0
  38. {llparse-0.1.0 → llparse-0.1.2}/llparse/pyfront/transform.py +0 -0
  39. {llparse-0.1.0 → llparse-0.1.2}/llparse.egg-info/dependency_links.txt +0 -0
  40. {llparse-0.1.0 → llparse-0.1.2}/llparse.egg-info/top_level.txt +0 -0
  41. {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.0
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
+ [![PyPI version](https://badge.fury.io/py/llparse.svg)](https://badge.fury.io/py/llparse)
13
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/llparse)](https://badge.fury.io/py/llparse)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://badge.fury.io/py/llparse.svg)](https://badge.fury.io/py/llparse)
3
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/llparse)](https://badge.fury.io/py/llparse)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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('#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 */')
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(" /* UNREACHABLE */;")
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(" /* UNREACHABLE */;")
1004
- out.append(" abort();")
1004
+ out.append(" UNREACHABLE;")
1005
1005
  out.append("}")
1006
1006
 
1007
1007
  def addState(self, state: str, lines: list[str]):
@@ -2,6 +2,7 @@
2
2
 
3
3
  from contextlib import contextmanager
4
4
  from typing import Optional
5
+
5
6
  from .frontend import IFrontendResult
6
7
  from .pyfront.front import Match
7
8
  from .pyfront.nodes import Invoke
@@ -208,6 +208,3 @@ class Dot:
208
208
 
209
209
  def escape(self, value: str):
210
210
  return "'" + value.replace("\\", "\\$1").replace('"', "\\$1") + "'"
211
-
212
-
213
- # TODO FIX ALL BUFFERS BACK TO STRINGS!
@@ -0,0 +1,2 @@
1
+ class Error(Exception):
2
+ pass
@@ -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
- 'non-leaf trie child of "%s" prevents table allocation' % node.name
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
- 'value passing trie leaf of "%s" prevents table allocation'
322
- % node.name
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 (DEFAULT_MAX_TABLE_WIDTH, DEFAULT_MIN_TABLE_SIZE,
8
- Frontend, IImplementation, source)
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,6 +1,6 @@
1
1
  from typing import Literal, Optional, Union
2
2
 
3
- import pyparse.pybuilder.main_code as code
3
+ from ..pybuilder import main_code as code
4
4
 
5
5
  # from pydot import graph_from_dot_data
6
6
 
@@ -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
- for _ in range(SIZE):
30
- self.words.append(0)
31
-
32
- if len(value) > 1:
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
- return True if (self.value == other.value) else False
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
- return list(res)
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
- self.lattice.update({(node, EMPTY_VALUE) for node in nodes})
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
- self.visit(root, list(changed))
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: "Lattice" = self.terminate(node, value, changed)
147
- if terminated.value == EMPTY_VALUE.value:
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] = dict()
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, value: Lattice, changed: set[Node]):
187
- if self.terminatedCache.get(node):
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
- self.terminatedCache[node]
209
- if self.terminatedCache.get(node)
210
- else EMPTY_VALUE
242
+ EMPTY_VALUE
243
+ if node not in self.terminatedCache
244
+ else self.terminatedCache[node]
211
245
  )
212
- if terminated.isEqual(EMPTY_VALUE):
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 not (not edge.key or isinstance(edge.key, int)):
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
- try:
229
- return path.index(obj)
230
- except Exception:
231
- return -1
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) == 0:
235
- raise Exception(
236
- f'Detected a loop in "{edge.node.name}" though : {edge.node.name}'
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 Exception(
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.extend([edge.node]))
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: str, name: str) -> None:
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__(self, signature: str, name: str, field: str) -> None:
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
- # NOTE I Added for typehinting Here as it refuses to show up on the ide I'm using (Vizonex)
52
-
53
- def __init__(self, signature: str, name: str, field: str, value: int) -> None:
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
- return hash(self.ref)
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:
@@ -0,0 +1,3 @@
1
+ from ..pyfront import front as code
2
+ from ..pyfront import nodes as node
3
+ from ..pyfront import transform as transform