llparse 0.1.2__tar.gz → 0.1.4__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.2 → llparse-0.1.4}/PKG-INFO +7 -4
  2. {llparse-0.1.2 → llparse-0.1.4}/README.md +6 -3
  3. {llparse-0.1.2 → llparse-0.1.4}/llparse/compilator.py +169 -8
  4. {llparse-0.1.2 → llparse-0.1.4}/llparse/debug.py +0 -3
  5. {llparse-0.1.2 → llparse-0.1.4}/llparse/frontend.py +79 -60
  6. {llparse-0.1.2 → llparse-0.1.4}/llparse/llparse.py +12 -2
  7. llparse-0.1.4/llparse/pybuilder/__init__.py +8 -0
  8. {llparse-0.1.2 → llparse-0.1.4}/llparse/pybuilder/builder.py +37 -1
  9. {llparse-0.1.2 → llparse-0.1.4}/llparse/pybuilder/main_code.py +35 -0
  10. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/front.py +0 -3
  11. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/implementation.py +3 -0
  12. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/nodes.py +33 -18
  13. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/peephole.py +1 -1
  14. {llparse-0.1.2 → llparse-0.1.4}/llparse/trie.py +10 -8
  15. {llparse-0.1.2 → llparse-0.1.4}/llparse.egg-info/PKG-INFO +7 -4
  16. {llparse-0.1.2 → llparse-0.1.4}/llparse.egg-info/SOURCES.txt +1 -0
  17. {llparse-0.1.2 → llparse-0.1.4}/pyproject.toml +1 -1
  18. llparse-0.1.4/tests/test_frontend.py +35 -0
  19. llparse-0.1.2/llparse/pybuilder/__init__.py +0 -2
  20. {llparse-0.1.2 → llparse-0.1.4}/LICENSE +0 -0
  21. {llparse-0.1.2 → llparse-0.1.4}/llparse/C_compiler.py +0 -0
  22. {llparse-0.1.2 → llparse-0.1.4}/llparse/__init__.py +0 -0
  23. {llparse-0.1.2 → llparse-0.1.4}/llparse/constants.py +0 -0
  24. {llparse-0.1.2 → llparse-0.1.4}/llparse/cython_builder.py +0 -0
  25. {llparse-0.1.2 → llparse-0.1.4}/llparse/dot.py +0 -0
  26. {llparse-0.1.2 → llparse-0.1.4}/llparse/enumerator.py +0 -0
  27. {llparse-0.1.2 → llparse-0.1.4}/llparse/errors.py +0 -0
  28. {llparse-0.1.2 → llparse-0.1.4}/llparse/header.py +0 -0
  29. {llparse-0.1.2 → llparse-0.1.4}/llparse/pybuilder/loopchecker.py +0 -0
  30. {llparse-0.1.2 → llparse-0.1.4}/llparse/pybuilder/parsemap.py +0 -0
  31. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/containers.py +0 -0
  32. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/namespace.py +0 -0
  33. {llparse-0.1.2 → llparse-0.1.4}/llparse/pyfront/transform.py +0 -0
  34. {llparse-0.1.2 → llparse-0.1.4}/llparse/settings.py +0 -0
  35. {llparse-0.1.2 → llparse-0.1.4}/llparse/spanalloc.py +0 -0
  36. {llparse-0.1.2 → llparse-0.1.4}/llparse/test.py +0 -0
  37. {llparse-0.1.2 → llparse-0.1.4}/llparse.egg-info/dependency_links.txt +0 -0
  38. {llparse-0.1.2 → llparse-0.1.4}/llparse.egg-info/top_level.txt +0 -0
  39. {llparse-0.1.2 → llparse-0.1.4}/setup.cfg +0 -0
  40. {llparse-0.1.2 → llparse-0.1.4}/tests/test_loop_checker.py +0 -0
  41. {llparse-0.1.2 → llparse-0.1.4}/tests/test_span_allocator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llparse
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -15,7 +15,7 @@ Dynamic: license-file
15
15
 
16
16
  A python parody of the typescript library llparse.
17
17
 
18
- I take no credit for the orginal work done by indutny and I was originally very nervous about making
18
+ I take no credit for the orginal work done by indutny and the other node-js contributors involved and I was originally very nervous about making
19
19
  this python library that I made public...
20
20
 
21
21
  Links to the original library
@@ -49,7 +49,7 @@ do it at your own risk. They maybe incompleted or not throughly stress-tested.
49
49
 
50
50
  ## New Features
51
51
  - Throw me an issue if typescript llparse introduces something new that you want for me or another contributor to try and implement
52
- just seeing llparse add new features is nothing but exciting to me.
52
+ just seeing llparse add new features is exciting to me.
53
53
 
54
54
  - If you want a feature that typescript llparse doesn't have, be sure to try making a pull request over there as well and not just here,
55
55
  there's a good chance they will appericate you for helping over there too and your helping make llhttp better by doing so. :)
@@ -62,7 +62,7 @@ there's a good chance they will appericate you for helping over there too and yo
62
62
  - Make it easy for me or someone else to find a problem and solve it in typescript after testing it in python
63
63
  - Typescript takes 2 commands to run a script with node-js it while python only takes one cutting the time required tremendously...
64
64
  - The orginal project was MIT licensed.
65
- - I wanted to write my own C Parsering tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
65
+ - I wanted to write my own C Parser tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
66
66
  - I didn't like __Lemon Parser__ or __Yacc__ all that much and a good ide for handling them in Visual Studio Code with error checking to my knowlegde does not exist.
67
67
  - The closest thing I got to what I wanted was a project named __NMFU__ shorthand for no memory for you and even I had problems with writing things using that library...
68
68
 
@@ -131,3 +131,6 @@ print(c.c)
131
131
  open("http_parser.c", "w").write(c.c)
132
132
  open("http_parser.h", "w").write(c.header)
133
133
  ```
134
+
135
+ ## Video Showcasing this library
136
+ - https://youtu.be/YQOzJ2BghQw
@@ -5,7 +5,7 @@
5
5
 
6
6
  A python parody of the typescript library llparse.
7
7
 
8
- I take no credit for the orginal work done by indutny and I was originally very nervous about making
8
+ I take no credit for the orginal work done by indutny and the other node-js contributors involved and I was originally very nervous about making
9
9
  this python library that I made public...
10
10
 
11
11
  Links to the original library
@@ -39,7 +39,7 @@ do it at your own risk. They maybe incompleted or not throughly stress-tested.
39
39
 
40
40
  ## New Features
41
41
  - Throw me an issue if typescript llparse introduces something new that you want for me or another contributor to try and implement
42
- just seeing llparse add new features is nothing but exciting to me.
42
+ just seeing llparse add new features is exciting to me.
43
43
 
44
44
  - If you want a feature that typescript llparse doesn't have, be sure to try making a pull request over there as well and not just here,
45
45
  there's a good chance they will appericate you for helping over there too and your helping make llhttp better by doing so. :)
@@ -52,7 +52,7 @@ there's a good chance they will appericate you for helping over there too and yo
52
52
  - Make it easy for me or someone else to find a problem and solve it in typescript after testing it in python
53
53
  - Typescript takes 2 commands to run a script with node-js it while python only takes one cutting the time required tremendously...
54
54
  - The orginal project was MIT licensed.
55
- - I wanted to write my own C Parsering tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
55
+ - I wanted to write my own C Parser tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
56
56
  - I didn't like __Lemon Parser__ or __Yacc__ all that much and a good ide for handling them in Visual Studio Code with error checking to my knowlegde does not exist.
57
57
  - The closest thing I got to what I wanted was a project named __NMFU__ shorthand for no memory for you and even I had problems with writing things using that library...
58
58
 
@@ -121,3 +121,6 @@ print(c.c)
121
121
  open("http_parser.c", "w").write(c.c)
122
122
  open("http_parser.h", "w").write(c.header)
123
123
  ```
124
+
125
+ ## Video Showcasing this library
126
+ - https://youtu.be/YQOzJ2BghQw
@@ -337,7 +337,7 @@ class Node:
337
337
  out: list[str],
338
338
  node: IWrap[_frontend.node.Node],
339
339
  noAdvance: bool,
340
- value: Optional[int],
340
+ value: Optional[int] = None,
341
341
  ):
342
342
  ctx = self.compilation
343
343
  target = ctx.unwrapNode(node).build(ctx)
@@ -501,7 +501,7 @@ class Pause(Error):
501
501
 
502
502
  assert self.ref.otherwise
503
503
  otherwise = ctx.unwrapNode(self.ref.otherwise.node)
504
- out.append(f"{ctx.currentField()} = (void*) (intptr_t) {otherwise};")
504
+ out.append(f"{ctx.currentField()} = (void*) (intptr_t) {otherwise.cachedDecel};")
505
505
  out.append(f"return {STATE_ERROR};")
506
506
 
507
507
 
@@ -512,7 +512,8 @@ class Sequence(Node):
512
512
 
513
513
  def doBuild(self, out: list[str]):
514
514
  ctx = self.compilation
515
-
515
+ # TODO: llparse_match_t could be easily changed around to
516
+ # Something that can't be overlapped with when compiled with other parsers...
516
517
  out.append("llparse_match_t match_seq;")
517
518
  out.append("")
518
519
 
@@ -572,7 +573,7 @@ class Single(Node):
572
573
  else:
573
574
  ch = f"'{chr(e.key)}'"
574
575
 
575
- out.append(f" case {ch}:" + "{")
576
+ out.append(f" case {ch}: " + "{")
576
577
  tmp: list[str] = []
577
578
 
578
579
  # For now debug everything....
@@ -639,7 +640,7 @@ class SpanEnd(Node):
639
640
  # Invoke callback
640
641
  callback = ctx.buildCode(ctx.unwrapCode(self.ref.callback, True))
641
642
 
642
- out.append(f"err = {callback}({ctx.stateArg()}, start,{ctx.posArg()});")
643
+ out.append(f"err = {callback}({ctx.stateArg()}, start, {ctx.posArg()});")
643
644
 
644
645
  out.append("if (err != 0) {")
645
646
  tmp = []
@@ -676,6 +677,163 @@ class SpanEnd(Node):
676
677
  out.append(f"return {STATE_ERROR};")
677
678
 
678
679
 
680
+ # Based off arthurschreiber's work with Indutny's Tips and requests added to the mix.
681
+
682
+ # 0x80 I8
683
+ # 0x8000 I16
684
+ # 0x800000 I24
685
+ # 0x1000000 U24
686
+
687
+ class Int(Node):
688
+ def __init__(self, ref: _frontend.node.Int):
689
+ super().__init__(ref)
690
+ self.ref = ref
691
+ self.offset = ref.byteOffset
692
+ # I'm going to deviate from arthurschreiber's work a bit with indutny's suggestions.
693
+ # we should really be using bitwise operators like rshift and lshift
694
+ @property
695
+ def pair(self):
696
+ return self.compilation, self.compilation.stateField(self.ref.field)
697
+
698
+ def readInt8(self, out: list[str]) -> None:
699
+ ctx, index = self.pair
700
+ out.append(f"{index} = ((*{ctx.posArg()}) & 0x80);")
701
+
702
+ def readUInt8(self, out: list[str]) -> None:
703
+ ctx, index = self.pair
704
+ out.append(f"{index} = (*{ctx.posArg()});")
705
+
706
+ # LITTLE ENDIAN
707
+
708
+ def readInt16LE(self, out: list[str]) -> None:
709
+ ctx, index = self.pair
710
+ if self.offset == 0:
711
+ out.append(f"{index} = (*{ctx.posArg()});")
712
+ else:
713
+ # Since BE Belongs to performing << aka left shifts we do >> right shifts
714
+ out.append(f"{index} = ({index} >> 8) | ((*{ctx.posArg()}) & 0x80);")
715
+
716
+ def readUInt16LE(self, out: list[str]) -> None:
717
+ ctx, index = self.pair
718
+ if self.offset == 0:
719
+ out.append(f"{index} = (*{ctx.posArg()});")
720
+ else:
721
+ out.append(f"{index} = ({index} >> 8) | (*{ctx.posArg()});")
722
+
723
+ def readInt24LE(self, out: list[str]) -> None:
724
+ ctx, index = self.pair
725
+ if self.offset == 0:
726
+ out.append(f"{index} = (*{ctx.posArg()});")
727
+ elif self.offset == 1:
728
+ out.append(f"{index} = ({index} >> 8) | (*{ctx.posArg()});")
729
+ else:
730
+ out.append(f"{index} = ({index} >> 8) | ((*{ctx.posArg()}) & 0x80);")
731
+
732
+ def readUInt24LE(self, out: list[str]) -> None:
733
+ ctx, index = self.pair
734
+ if self.offset == 0:
735
+ out.append(f"{index} = (*{ctx.posArg()});")
736
+ else:
737
+ out.append(f"{index} = ({index} >> 8) | (*{ctx.posArg()});")
738
+
739
+ def readInt32LE(self, out: list[str]) -> None:
740
+ ctx, index = self.pair
741
+ if self.offset == 0:
742
+ out.append(f"{index} = (*{ctx.posArg()});")
743
+ elif self.offset in (1, 2):
744
+ out.append(f"{index} = ({index} >> 8) | (*{ctx.posArg()});")
745
+ else:
746
+ out.append(f"{index} = ({index} >> 8) | ((*{ctx.posArg()}) & 0x80);")
747
+
748
+ def readUInt32LE(self, out: list[str]) -> None:
749
+ ctx, index = self.pair
750
+ if self.offset == 0:
751
+ out.append(f"{index} = (*{ctx.posArg()});")
752
+ else:
753
+ out.append(f"{index} = ({index} >> 8) | (*{ctx.posArg()});")
754
+
755
+ # BIG ENDIAN
756
+
757
+ def readInt16BE(self, out: list[str]) -> None:
758
+ ctx, index = self.pair
759
+ if self.offset == 0:
760
+ out.append(f"{index} = (*{ctx.posArg()});")
761
+ else:
762
+ # Since LE Belongs to >> we do "<<" instead
763
+ out.append(f"{index} = ({index} << 8) | ((*{ctx.posArg()}) & 0x80);")
764
+
765
+ def readUInt16BE(self, out: list[str]) -> None:
766
+ ctx, index = self.pair
767
+ if self.offset == 0:
768
+ out.append(f"{index} = (*{ctx.posArg()});")
769
+ else:
770
+ out.append(f"{index} = ({index} << 8) | (*{ctx.posArg()});")
771
+
772
+ def readInt24BE(self, out: list[str]) -> None:
773
+ ctx, index = self.pair
774
+ if self.offset == 0:
775
+ out.append(f"{index} = (*{ctx.posArg()});")
776
+ elif self.offset == 1:
777
+ out.append(f"{index} = ({index} << 8) | (*{ctx.posArg()});")
778
+ else:
779
+ out.append(f"{index} = ({index} << 8) | ((*{ctx.posArg()}) & 0x80);")
780
+
781
+ def readUInt24BE(self, out: list[str]) -> None:
782
+ ctx, index = self.pair
783
+ if self.offset == 0:
784
+ out.append(f"{index} = (*{ctx.posArg()});")
785
+ else:
786
+ out.append(f"{index} = ({index} << 8) | (*{ctx.posArg()});")
787
+
788
+ def readInt32BE(self, out: list[str]) -> None:
789
+ ctx, index = self.pair
790
+ if self.offset == 0:
791
+ out.append(f"{index} = (*{ctx.posArg()});")
792
+ elif self.offset in (1, 2):
793
+ out.append(f"{index} = ({index} << 8) | (*{ctx.posArg()});")
794
+ else:
795
+ out.append(f"{index} = ({index} << 8) | ((*{ctx.posArg()}) & 0x80);")
796
+
797
+ def readUInt32BE(self, out: list[str]) -> None:
798
+ ctx, index = self.pair
799
+ if self.offset == 0:
800
+ out.append(f"{index} = (*{ctx.posArg()});")
801
+ else:
802
+ out.append(f"{index} = ({index} << 8) | (*{ctx.posArg()});")
803
+
804
+
805
+ def doBuild(self, out:list[str]):
806
+ self.prologue(out)
807
+ # I'm still supporting 3.9 but I plan to drop it's support in favor of match case soon...
808
+ bits = self.ref.bits
809
+
810
+ if self.compilation.getFieldType(self.ref.field) == 'ptr':
811
+ raise ValueError(f'property {self.ref.field} should not use pointers but it was given \"ptr\"')
812
+
813
+ if bits == 1:
814
+ self.readInt8(out) if self.ref.signed else self.readUInt8(out)
815
+ elif bits == 2:
816
+ if self.ref.littleEndian:
817
+ self.readInt16LE(out) if self.ref.signed else self.readUInt16LE(out)
818
+ else:
819
+ self.readInt16BE(out) if self.ref.signed else self.readUInt16BE(out)
820
+ elif bits == 3:
821
+ if self.ref.littleEndian:
822
+ self.readInt24LE(out) if self.ref.signed else self.readUInt24LE(out)
823
+ else:
824
+ self.readInt24BE(out) if self.ref.signed else self.readUInt24BE(out)
825
+ else:
826
+ if self.ref.littleEndian:
827
+ self.readInt32LE(out) if self.ref.signed else self.readUInt32LE(out)
828
+ else:
829
+ self.readInt32BE(out) if self.ref.signed else self.readUInt32BE(out)
830
+ # TODO: uint64 & int64
831
+
832
+ self.tailTo(out, self.ref.otherwise.node, self.ref.otherwise.noAdvance, None)
833
+
834
+
835
+
836
+
679
837
  MAX_CHAR = 0xFF
680
838
  TABLE_GROUP = 16
681
839
 
@@ -1077,13 +1235,14 @@ class Compilation:
1077
1235
  r = Consume(ref)
1078
1236
  elif isinstance(ref, _frontend.node.Empty):
1079
1237
  r = Empty(ref)
1238
+ elif isinstance(ref, _frontend.node.Pause):
1239
+ r = Pause(ref)
1240
+
1080
1241
  elif isinstance(ref, _frontend.node.Error):
1081
1242
  r = Error(ref)
1082
1243
  elif isinstance(ref, _frontend.node.Invoke):
1083
1244
  r = Invoke(ref)
1084
- elif isinstance(ref, _frontend.node.Pause):
1085
- r = Pause(ref)
1086
-
1245
+
1087
1246
  elif isinstance(ref, _frontend.node.SpanStart):
1088
1247
  r = SpanStart(ref)
1089
1248
 
@@ -1096,6 +1255,8 @@ class Compilation:
1096
1255
  r = Sequence(ref)
1097
1256
  elif isinstance(ref, _frontend.node.TableLookup):
1098
1257
  r = TableLookup(ref)
1258
+ elif isinstance(ref, _frontend.node.Int):
1259
+ r = Int(ref)
1099
1260
  else:
1100
1261
  raise TypeError(
1101
1262
  f'refrence "{ref}" is an Invalid Code Type , TypeName:"{ref.__class__.__name__}"'
@@ -9,9 +9,6 @@ class Debugger:
9
9
 
10
10
  while queue:
11
11
  node = queue.pop()
12
- print(node.name)
13
- if node.name == "nmethods":
14
- print(node.getEdges())
15
12
  if edges := node.getEdges():
16
13
  for slot in edges:
17
14
  if slot.node in nodes:
@@ -12,11 +12,15 @@ from .pyfront.implementation import IImplementation
12
12
  from .pyfront.nodes import ITableEdge
13
13
  from .pyfront.peephole import Peephole
14
14
  from .spanalloc import SpanAllocator
15
- from .trie import Trie, TrieEmpty, TrieNode, TrieSequence, TrieSingle
15
+ from .trie import Trie, TrieEmpty, TrieNode, TrieSequence, TrieSingle, ITrieSingleChild
16
16
 
17
17
  DEFAULT_MIN_TABLE_SIZE = 32
18
18
  DEFAULT_MAX_TABLE_WIDTH = 4
19
19
 
20
+ from logging import getLogger
21
+
22
+ log = getLogger("llparse.frontend")
23
+
20
24
 
21
25
  WrappedNode = IWrap[_frontend.node.Node]
22
26
  WrappedCode = IWrap[_frontend.code.Code]
@@ -166,7 +170,7 @@ class Frontend:
166
170
  trieNode = trie.build(list(node))
167
171
 
168
172
  if not trieNode:
169
- # print("[DEBUG]", "TrieNode was nonexistant")
173
+ log.debug("TrieNode was nonexistant")
170
174
  return self.implementation.node.Empty(
171
175
  _frontend.node.Empty(self.Id.id(node.name))
172
176
  )
@@ -178,7 +182,7 @@ class Frontend:
178
182
 
179
183
  return children
180
184
 
181
- def registerNode(self, node: WrappedNode):
185
+ def registerNode(self, node: WrappedNode) -> None:
182
186
  # NOTE NO Implementations required here since this is python!
183
187
  if isinstance(
184
188
  node.ref,
@@ -207,7 +211,7 @@ class Frontend:
207
211
  result = nodeImpl.Error(_frontend.node.Error(ID(), node.code, node.reason))
208
212
 
209
213
  elif isinstance(node, source.code.Pause):
210
- result = nodeImpl.Pause(_frontend.node.Error(ID(), node.code, node.reason))
214
+ result = nodeImpl.Pause(_frontend.node.Pause(ID(), node.code, node.reason))
211
215
 
212
216
  elif isinstance(node, source.code.Comsume):
213
217
  result = nodeImpl.Consume(_frontend.node.Consume(ID(), node.field))
@@ -240,6 +244,10 @@ class Frontend:
240
244
 
241
245
  elif isinstance(node, source.code.Match):
242
246
  result = self.translateMatch(node)
247
+
248
+ elif isinstance(node, source.node.Int):
249
+ result = self.translateInt(node)
250
+
243
251
  else:
244
252
  raise Exception(f'Unknown Node Type for :"{node.name}" {type(node)}')
245
253
 
@@ -247,24 +255,26 @@ class Frontend:
247
255
 
248
256
  if isinstance(result, list):
249
257
  # result:list[WrappedNode]
250
- assert isinstance(node, source.code.Match)
251
- _match = node
252
258
 
253
- if not otherwise:
254
- raise Exception(f'Node "{node.name}" has no ".otherwise()"')
259
+ assert isinstance(node, (source.code.Match, source.node.Int))
260
+ _match = node
261
+
262
+ assert otherwise, (f'Node "{node.name}" has no ".otherwise()"')
255
263
 
256
- else:
264
+ if isinstance(node, source.node.Match):
257
265
  for child in result:
258
266
  if not child.ref.otherwise:
259
267
  child.ref.setOtherwise(
260
268
  self.translate(otherwise.node), otherwise.noAdvance
261
269
  )
270
+ transform = self.translateTransform(_match.getTransform())
271
+ for child in result:
272
+ # TODO Vizonex : This might break , be sure to make a workaround function here...
273
+ child.ref.setTransform(transform)
262
274
 
263
- transform = self.translateTransform(_match.getTransform())
264
- for child in result:
265
- # TODO Vizonex : This might break , be sure to make a workaround function here...
266
- child.ref.setTransform(transform)
267
-
275
+
276
+ else:
277
+ result[-1].ref.setOtherwise(self.translate(otherwise.node), otherwise.noAdvance)
268
278
  assert len(result) >= 1
269
279
  return result[0]
270
280
 
@@ -287,7 +297,6 @@ class Frontend:
287
297
 
288
298
  if isinstance(single.ref, _frontend.node.Invoke):
289
299
  for edge in node:
290
- # print(edge.key)
291
300
  single.ref.addEdge(
292
301
  ord(edge.key) if isinstance(edge.key, str) else edge.key,
293
302
  self.translate(edge.node),
@@ -296,6 +305,23 @@ class Frontend:
296
305
  assert len(list(node)) == 0
297
306
 
298
307
  return single
308
+
309
+ def translateInt(self, node: source.node.Int) -> list[IWrap[_frontend.node.Int]]:
310
+ inner = _frontend.node.Int(self.Id.id(node.name), node.field, node.bits, node.signed, node.little_endian, 0)
311
+ result = [self.implementation.node.Int(inner)]
312
+ # front is to avoid overlapping with python's functions (aka next)
313
+ front = self.Map[node] = result[0]
314
+
315
+ for offset in range(1, node.bits):
316
+ unique_name = self.Id.id(f"{node.name}_byte{offset + 1}")
317
+ inner = _frontend.node.Int(unique_name, node.field, node.bits, node.signed, node.little_endian, offset)
318
+ outer = self.implementation.node.Int(inner)
319
+ result.append(outer)
320
+ # Integers will advance since they are unpacking values...
321
+ front.ref.setOtherwise(outer, False)
322
+ front = result[-1]
323
+ return result
324
+
299
325
 
300
326
  def maybeTableLookup(
301
327
  self, node: source.code.Match, trie: TrieSingle, children: MatchChildren
@@ -304,52 +330,48 @@ class Frontend:
304
330
  return None
305
331
 
306
332
  targets: dict[source.code.Node, ITableLookupTarget] = {}
307
- bailout = False
308
- for child in trie.children:
309
- if isinstance(child.node, TrieEmpty):
310
- # print(
311
- # 'non-leaf trie child of "%s" prevents table allocation' % node.name
312
- # )
313
- bailout = False
314
- continue
315
-
316
- empty: TrieEmpty = child.node
317
- if getattr(empty, "value", None) is None:
318
- # print(
319
- # 'value passing trie leaf of "%s" prevents table allocation'
320
- # % node.name
321
- # )
322
- bailout = False
323
- continue
333
+
334
+ def check_child(child: ITrieSingleChild):
335
+ nonlocal targets
336
+ if not isinstance(child.node, TrieEmpty):
337
+ log.debug(
338
+ 'non-leaf trie child of "%s" prevents table allocation' % node.name
339
+ )
340
+ return False
341
+ empty = child.node
342
+ if empty.value is not None:
343
+ log.debug(
344
+ 'value passing trie leaf of "%s" prevents table allocation'
345
+ % node.name
346
+ )
347
+ return False
324
348
 
325
349
  target = empty.node
326
- if not targets.get(target):
350
+ if target not in targets:
327
351
  targets[target] = ITableLookupTarget(
328
352
  keys=[child.key], noAdvance=child.noAdvance, trie=empty
329
353
  )
330
- bailout = True
331
- break
354
+ return True
332
355
 
333
356
  existing = targets[target]
334
-
335
357
  if existing.noAdvance != child.noAdvance:
336
- # print('noAdvance mismatch in a trie leaf of "%s" prevents table allocation' % node.name)
337
- bailout = False
338
- break
339
-
358
+ log.debug(
359
+ f'noAdvance mismatch in a trie leaf of "{node.name}" prevents '
360
+ "table allocation"
361
+ )
362
+ return False
340
363
  existing.keys.append(child.key)
364
+ return True
341
365
 
342
- # TODO: see if breaking or continue block after out is breakout has been determined is good ot not...
343
- bailout = True
344
- break
345
-
346
- # assert len(trie.children) == len(targets), "Something went wrong"
347
- if bailout:
366
+ if not all([check_child(child) for child in trie.children]):
348
367
  return
349
368
 
350
369
  # Weave width limit for optimization...
351
- if len(targets.keys()) >= (1 << self.options["maxTableElemWidth"]):
352
- # print('too many different trie targets of "%s" for a table allocation' % node.name)
370
+ if len(targets) >= (1 << self.options["maxTableElemWidth"]):
371
+ log.debug(
372
+ 'too many different trie targets of "%s" for a table allocation'
373
+ % node.name
374
+ )
353
375
  return
354
376
 
355
377
  table = self.implementation.node.TableLookup(
@@ -358,11 +380,11 @@ class Frontend:
358
380
  children.append(table)
359
381
 
360
382
  # Break Loop
361
- if self.Map.get(node):
383
+ if not self.Map.get(node):
362
384
  self.Map[node] = table
363
385
 
364
386
  for target in targets.values():
365
- _next = self.translateTrie(node, target, children)
387
+ _next = self.translateTrie(node, target.trie, children)
366
388
  table.ref.addEdge(
367
389
  ITableEdge(keys=target.keys, noAdvance=target.noAdvance, node=_next)
368
390
  )
@@ -408,9 +430,7 @@ class Frontend:
408
430
  self, node: source.code.Match, trie: TrieSingle, children: MatchChildren
409
431
  ):
410
432
  # Check if Tablelookup could be a valid option to Optimze our code up...
411
- maybeTable = self.maybeTableLookup(node, trie, children)
412
-
413
- if maybeTable:
433
+ if maybeTable := self.maybeTableLookup(node, trie, children):
414
434
  return maybeTable
415
435
 
416
436
  single = self.implementation.node.Single(
@@ -432,8 +452,7 @@ class Frontend:
432
452
  value=child.node.value if isinstance(child.node, TrieEmpty) else None,
433
453
  )
434
454
 
435
- otherwise = trie.otherwise
436
- if otherwise:
455
+ if otherwise := trie.otherwise:
437
456
  single.ref.setOtherwise(
438
457
  self.translateTrie(node, otherwise, children), True, otherwise.value
439
458
  )
@@ -442,8 +461,9 @@ class Frontend:
442
461
  def translateSpanCode(self, code: source.code._Span):
443
462
  return self.translateCode(code)
444
463
 
445
- # TODO Vizonex Maybe better typehining can be used in this function alone....
446
- def translateCode(self, code: source.code.Code):
464
+ def translateCode(
465
+ self, code: source.code.Code
466
+ ):
447
467
  """Translates Builder Classes to Frontend Classes..."""
448
468
 
449
469
  prefixed = self.codeId.id(code.name).name
@@ -499,9 +519,8 @@ class Frontend:
499
519
  else:
500
520
  raise Exception(f'UnSupported code:"{code.name}" type: "{type(code)}"')
501
521
 
502
- if self.codeCache.get(res.ref.cacheKey):
503
- return self.codeCache[res.ref.cacheKey]
504
-
522
+ if _res := self.codeCache.get(res.ref.cacheKey):
523
+ return _res
505
524
  self.codeCache[res.ref.cacheKey] = res
506
525
  return res
507
526
 
@@ -67,12 +67,20 @@ class Compiler:
67
67
  properties: list[source.Property],
68
68
  header_name: Optional[str] = None,
69
69
  Impl: Optional[IImplementation] = IImplementation(),
70
+ override_llparse_name: bool = False
70
71
  ):
71
72
  """Creates the C and header file..."""
72
73
  info = self.to_frontend(root, properties, Impl)
73
74
  hb = HeaderBuilder(self.prefix, self.headerGuard, properties, info.spans)
75
+ cdata = CCompiler(header_name, self.debug).compile(info)
76
+ if override_llparse_name:
77
+ # sometimes users want to combine parsers together when compiling with C
78
+ # to make up for conflicts with other parsers example: llhttp
79
+ # there should be a fair way of compiling everything.
80
+ cdata = cdata.replace('llparse', self.prefix)
81
+
74
82
  return CompilerResult(
75
- CCompiler(header_name, self.debug).compile(info), hb.build()
83
+ cdata , hb.build()
76
84
  )
77
85
 
78
86
 
@@ -122,6 +130,7 @@ class LLParse(source.Builder):
122
130
  maxTableElemWidth: Optional[int] = None,
123
131
  minTableSize: Optional[int] = None,
124
132
  header_name: Optional[str] = None,
133
+ override_llparse_name:bool = False
125
134
  ):
126
135
  """Builds Graph and then compiles the data into C code , returns with the header and C file inside of a Dataclass"""
127
136
 
@@ -133,7 +142,8 @@ class LLParse(source.Builder):
133
142
  minTableSize if minTableSize else DEFAULT_MIN_TABLE_SIZE,
134
143
  )
135
144
 
136
- return compiler.compile(root, self.properties(), header_name=header_name)
145
+ return compiler.compile(root, self.properties(), header_name=header_name, override_llparse_name=override_llparse_name)
146
+
137
147
 
138
148
  def to_frontend(
139
149
  self,
@@ -0,0 +1,8 @@
1
+ from ..pybuilder.builder import *
2
+ from ..pybuilder.loopchecker import *
3
+ from ..pybuilder.main_code import (
4
+ # I'll add more soon I feel a little lazy at the moment.
5
+ Node,
6
+ Match,
7
+ Int
8
+ )
@@ -1,7 +1,8 @@
1
1
  from typing import Literal, Optional, Union
2
-
3
2
  from ..pybuilder import main_code as code
4
3
 
4
+ # typehinting node and code (TODO: Vizonex) Lets seperate the modules soon...
5
+ node = code
5
6
  # from pydot import graph_from_dot_data
6
7
 
7
8
 
@@ -316,3 +317,38 @@ class Builder:
316
317
  def properties(self) -> list[Property]:
317
318
  """Return list of all allocated properties in parser's state."""
318
319
  return list(self.privProperties.values())
320
+
321
+ def intBE(self, field: str, bits:int):
322
+ """
323
+ :param field: State's property name
324
+ :param bits: Number of bits to use
325
+ """
326
+ return code.Int(field, bits, True, False)
327
+
328
+ def intLE(self, field: str, bits: int):
329
+ """
330
+ return a node for unpacking arrays to integers
331
+
332
+ :param field: State's property name
333
+ :param bits: Number of bits to use
334
+ """
335
+ return code.Int(field, bits, True, True)
336
+
337
+ def uintBE(self, field: str, bits: int):
338
+ """
339
+ return a node for unpacking arrays to integers
340
+
341
+ :param field: State's property name
342
+ :param bits: Number of bits to use
343
+ """
344
+ return code.Int(field, bits, False, False)
345
+
346
+ def uintLE(self, field: str, bits: int):
347
+ """
348
+ return a node for unpacking arrays to integers
349
+
350
+ :param field: State's property name
351
+ :param bits: Number of bits to use
352
+ """
353
+ return code.Int(field, bits, False, True)
354
+
@@ -92,6 +92,8 @@ class _Match(Code):
92
92
  super().__init__("match", name)
93
93
 
94
94
 
95
+
96
+
95
97
  @dataclass
96
98
  class IMulAddOptions:
97
99
  base: int
@@ -244,6 +246,39 @@ class Invoke(Node):
244
246
  self.addEdge(Edge(targetNode, True, numKey, None))
245
247
 
246
248
 
249
+
250
+ # Not in llparse node-js (yet) But I wanted to implement
251
+ # this into my version since I am making a very important
252
+ # http2 frame parser
253
+
254
+ # SEE: https://github.com/nodejs/llparse-frontend/pull/1
255
+
256
+ def build_name(field:str, bits: int, signed:bool, little_endian:bool) -> str:
257
+ result = f"{field}_{'int' if signed else 'uint'}_{bits * 8}"
258
+ if bits > 1:
259
+ return result + ('_le' if little_endian else 'be')
260
+ else:
261
+ return result
262
+
263
+
264
+ class Int(Node):
265
+ """Used for parsing bytes via unpacking"""
266
+ def __init__(self, field: str, bits: int, signed: bool, little_endian: bool) -> None:
267
+ """
268
+ :param field: State's property name
269
+ :param bits: Number of bits to use
270
+ :param signed: Number is signed
271
+ :param little_endian: true if le, false if be
272
+ """
273
+ if bits < 0:
274
+ raise ValueError("bits should be a positive integer")
275
+ self.field = field
276
+ self.bits = bits
277
+ self.signed = signed
278
+ self.little_endian = little_endian
279
+ super().__init__(build_name(field, bits, signed, little_endian))
280
+
281
+
247
282
  # -- Transfroms --
248
283
 
249
284
  TransformName = ["to_lower_unsafe", "to_lower"]
@@ -14,9 +14,6 @@ Signature = TypeVar("Signature", bytes, str)
14
14
  class IWrap(Generic[T]):
15
15
  ref: T
16
16
 
17
- # def __hash__(self) -> int:
18
- # return hash(self.ref)
19
-
20
17
 
21
18
  def toCacheKey(value: Union[int, bool]) -> str:
22
19
  if isinstance(value, int):
@@ -37,6 +37,9 @@ class INodeImplementation:
37
37
 
38
38
  def TableLookup(self, n: node.TableLookup):
39
39
  return IWrap(n)
40
+
41
+ def Int(self, n: node.Int):
42
+ return IWrap(n)
40
43
 
41
44
 
42
45
  class ITransformImplementation:
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Any, Optional
2
+ from typing import Any, Optional, Callable
3
3
 
4
4
  from ..pyfront.front import Code, IWrap, Span, SpanField
5
5
  from ..pyfront.transform import Transform
@@ -8,7 +8,9 @@ from ..pyfront.transform import Transform
8
8
  class Slot:
9
9
  # ONLY NODE SHOULD BE ALLOWED TO BE SEEN
10
10
 
11
- def __init__(self, node: IWrap["Node"], value: Any) -> None:
11
+ def __init__(
12
+ self, node: IWrap["Node"], value: Callable[[IWrap["Node"]], None]
13
+ ) -> None:
12
14
  self.privNode = node
13
15
  """Same as calling it from get and setting the value etc..."""
14
16
  self.privUpdate = value
@@ -19,12 +21,17 @@ class Slot:
19
21
  # I spent 4 hours trying to figure this how this could be implemented
20
22
  # so this is my only ideal sloution
21
23
  def __hash__(self) -> int:
22
- return hash(self.privUpdate.ref.id.name)
24
+ return hash(self.privNode.ref.id.name)
23
25
 
24
26
  @property
25
27
  def node(self):
26
28
  return self.privNode
27
29
 
30
+ @node.setter
31
+ def node(self, value: IWrap["Node"]):
32
+ self.privNode = value
33
+ self.privUpdate(value)
34
+
28
35
 
29
36
  @dataclass(unsafe_hash=True)
30
37
  class IUniqueName:
@@ -121,9 +128,6 @@ class Invoke(Node):
121
128
 
122
129
 
123
130
  class Empty(Node):
124
- # def __init__(self, id: IUniqueName) -> None:
125
- # super().__init__(id)
126
-
127
131
  def __hash__(self):
128
132
  return hash(self.id)
129
133
 
@@ -149,6 +153,26 @@ class Pause(Error):
149
153
  super().__init__(id, code, reason)
150
154
 
151
155
 
156
+ # Not in llparse node-js (yet) But I wanted to implement
157
+ # this into my version since I am making a very important
158
+ # http2 frame parser
159
+
160
+ # SEE: https://github.com/nodejs/llparse-frontend/pull/1
161
+
162
+ @dataclass
163
+ class Int(Node):
164
+ field: str
165
+ bits: int
166
+ signed: bool
167
+ littleEndian: bool
168
+ byteOffset: int
169
+
170
+ def __hash__(self):
171
+ return hash(self.id)
172
+
173
+
174
+
175
+
152
176
  @dataclass
153
177
  class ISeqEdge:
154
178
  node: IWrap[Node]
@@ -237,16 +261,7 @@ class TableLookup(Match):
237
261
  self.privEdges.append(edge)
238
262
 
239
263
  def buildSlots(self):
240
- edge = self.privEdges
241
- for e in edge:
242
- yield Slot(e.node, e.node)
243
- for e in super().buildSlots():
244
- yield e
245
-
246
-
247
- # Ident = Identifier("llComment")
248
-
249
- # node = Node(Ident.id("__On_Pagesum"))
264
+ for e in self.privEdges:
265
+ yield Slot(e.node, lambda value: setattr(e, "node", value))
266
+ yield from super().buildSlots()
250
267
 
251
- # node2 = Invoke(Ident.id("Check_Flag"),IsEqual("Flag","Check_Flag",0))
252
- # node2.setOtherwise(IWrap(node),True,0)
@@ -11,7 +11,7 @@ class Peephole:
11
11
  def optimize(self, root: WrapNode, nodes: WrapList):
12
12
  changed = set(nodes)
13
13
 
14
- while len(changed) != 0:
14
+ while changed:
15
15
  previous = changed
16
16
  changed = set()
17
17
 
@@ -58,7 +58,7 @@ class Trie:
58
58
  internalEdges: list[IEdge] = []
59
59
 
60
60
  for edge in edges:
61
- key = str(edge.key) if isinstance(edge.key, int) else edge.key
61
+ key = chr(edge.key) if isinstance(edge.key, int) else edge.key
62
62
  internalEdges.append(
63
63
  IEdge(
64
64
  key=key.encode("utf-8") if isinstance(key, str) else key,
@@ -73,8 +73,8 @@ class Trie:
73
73
  def level(self, edges: list[IEdge], path: list[bytes] = []):
74
74
  first = edges[0].key
75
75
  last = edges[-1].key
76
- # print("level",edges, first)
77
- if len(edges) == 1 and len(edges[0].key) == 0:
76
+ # print("level", edges, first)
77
+ if len(edges) == 1 and (len(edges[0].key) == 0):
78
78
  return TrieEmpty(edges[0].node, edges[0].value)
79
79
 
80
80
  i = 0
@@ -112,17 +112,17 @@ class Trie:
112
112
  + (b", ".join(path).decode("utf-8"))
113
113
  + "]"
114
114
  )
115
-
115
+
116
116
  keys: dict[int, list[IEdge]] = {}
117
117
  otherwise = None
118
118
  for edge in edges:
119
- if not len(edge.key):
119
+ if not edge.key:
120
120
  otherwise = TrieEmpty(edge.node, edge.value)
121
121
  continue
122
122
 
123
123
  key = edge.key[0]
124
124
 
125
- if keys.get(key):
125
+ if key in keys:
126
126
  keys[key].append(edge)
127
127
  else:
128
128
  keys[key] = [edge]
@@ -146,7 +146,9 @@ class Trie:
146
146
  + "]"
147
147
  )
148
148
  raise TypeError(err)
149
- child = ITrieSingleChild(key, noAdvance, self.level(sliced, subPath))
150
- children.append(child)
149
+
150
+ children.append(
151
+ ITrieSingleChild(key, noAdvance, self.level(sliced, subPath))
152
+ )
151
153
 
152
154
  return TrieSingle(children, otherwise)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llparse
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -15,7 +15,7 @@ Dynamic: license-file
15
15
 
16
16
  A python parody of the typescript library llparse.
17
17
 
18
- I take no credit for the orginal work done by indutny and I was originally very nervous about making
18
+ I take no credit for the orginal work done by indutny and the other node-js contributors involved and I was originally very nervous about making
19
19
  this python library that I made public...
20
20
 
21
21
  Links to the original library
@@ -49,7 +49,7 @@ do it at your own risk. They maybe incompleted or not throughly stress-tested.
49
49
 
50
50
  ## New Features
51
51
  - Throw me an issue if typescript llparse introduces something new that you want for me or another contributor to try and implement
52
- just seeing llparse add new features is nothing but exciting to me.
52
+ just seeing llparse add new features is exciting to me.
53
53
 
54
54
  - If you want a feature that typescript llparse doesn't have, be sure to try making a pull request over there as well and not just here,
55
55
  there's a good chance they will appericate you for helping over there too and your helping make llhttp better by doing so. :)
@@ -62,7 +62,7 @@ there's a good chance they will appericate you for helping over there too and yo
62
62
  - Make it easy for me or someone else to find a problem and solve it in typescript after testing it in python
63
63
  - Typescript takes 2 commands to run a script with node-js it while python only takes one cutting the time required tremendously...
64
64
  - The orginal project was MIT licensed.
65
- - I wanted to write my own C Parsering tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
65
+ - I wanted to write my own C Parser tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
66
66
  - I didn't like __Lemon Parser__ or __Yacc__ all that much and a good ide for handling them in Visual Studio Code with error checking to my knowlegde does not exist.
67
67
  - The closest thing I got to what I wanted was a project named __NMFU__ shorthand for no memory for you and even I had problems with writing things using that library...
68
68
 
@@ -131,3 +131,6 @@ print(c.c)
131
131
  open("http_parser.c", "w").write(c.c)
132
132
  open("http_parser.h", "w").write(c.header)
133
133
  ```
134
+
135
+ ## Video Showcasing this library
136
+ - https://youtu.be/YQOzJ2BghQw
@@ -33,5 +33,6 @@ llparse/pyfront/namespace.py
33
33
  llparse/pyfront/nodes.py
34
34
  llparse/pyfront/peephole.py
35
35
  llparse/pyfront/transform.py
36
+ tests/test_frontend.py
36
37
  tests/test_loop_checker.py
37
38
  tests/test_span_allocator.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "llparse"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "A Parody of llparse written for writing C Parsers with Python"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,35 @@
1
+
2
+ from llparse import LLParse
3
+
4
+
5
+
6
+ def test_build_tables():
7
+ # There was a bug with 1.2 of our version that doesn't affect the node-js one where
8
+ # it wouldn't building tables, this attempts to simulate the problem Currenlty this
9
+ # bug is patched now :)
10
+ p = LLParse("lltable")
11
+ start = p.node('start')
12
+ loop = p.node('loop')
13
+ loop.skipTo(start)
14
+ start.match(
15
+ [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122,
16
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 58,
17
+ 59, 60, 61, 62, 63, 64, 91, 92, 93, 94, 95, 96, 123, 124, 125, 126, 32, 9, 10, 13, 11, 12],
18
+ loop
19
+ ).otherwise(p.error(0, 'im a little teapot'))
20
+
21
+ # If there is not a lookup_table this then it has failed me ;-;
22
+ assert "lookup_table" in p.build(start).c
23
+
24
+
25
+ def test_pausing():
26
+ # Ensure frotentend LoopChecker does not mark off against Pausing
27
+ p = LLParse("lltest")
28
+ s = p.node('start')
29
+ s2 = p.node('start2')
30
+ s.match('p', p.pause(1, 'parser was asked to pause').otherwise(s2)).skipTo(s)
31
+ s2.match('p', p.pause(2, 'parser was asked to pause again').otherwise(s)).skipTo(s2)
32
+
33
+ p.build(s)
34
+
35
+
@@ -1,2 +0,0 @@
1
- from ..pybuilder.builder import *
2
- from ..pybuilder.loopchecker import *
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes