llparse 0.1.0__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.
- llparse/C_compiler.py +204 -0
- llparse/__init__.py +2 -0
- llparse/compilator.py +1190 -0
- llparse/constants.py +48 -0
- llparse/cython_builder.py +311 -0
- llparse/debug.py +23 -0
- llparse/dot.py +213 -0
- llparse/enumerator.py +20 -0
- llparse/frontend.py +527 -0
- llparse/header.py +89 -0
- llparse/llparse.py +150 -0
- llparse/pybuilder/__init__.py +2 -0
- llparse/pybuilder/builder.py +318 -0
- llparse/pybuilder/loopchecker.py +246 -0
- llparse/pybuilder/main_code.py +548 -0
- llparse/pybuilder/parsemap.py +37 -0
- llparse/pyfront/containers.py +33 -0
- llparse/pyfront/front.py +189 -0
- llparse/pyfront/implementation.py +98 -0
- llparse/pyfront/namespace.py +1 -0
- llparse/pyfront/nodes.py +243 -0
- llparse/pyfront/peephole.py +45 -0
- llparse/pyfront/transform.py +21 -0
- llparse/settings.py +285 -0
- llparse/spanalloc.py +176 -0
- llparse/test.py +232 -0
- llparse/tire.py +158 -0
- llparse/trie.py +165 -0
- llparse-0.1.0.dist-info/METADATA +129 -0
- llparse-0.1.0.dist-info/RECORD +33 -0
- llparse-0.1.0.dist-info/WHEEL +5 -0
- llparse-0.1.0.dist-info/licenses/LICENSE +21 -0
- llparse-0.1.0.dist-info/top_level.txt +1 -0
llparse/settings.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compiles a settings module to use alongside llparse
|
|
3
|
+
|
|
4
|
+
This is mostly a concept idea for llparse inspired by llhttp to include a settings
|
|
5
|
+
module in the future to make Writing the span callbacks and other important things
|
|
6
|
+
less painful to edit and write entirely.
|
|
7
|
+
|
|
8
|
+
NOTE: That The objects being passed would come directly from llparse-builder
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Optional, Union
|
|
14
|
+
|
|
15
|
+
from .llparse import LLParse
|
|
16
|
+
from .pybuilder import main_code as source
|
|
17
|
+
|
|
18
|
+
# MACRO Names , These can be renamed to
|
|
19
|
+
# prevent mixing with llhttp or other parsers
|
|
20
|
+
# allow for mulitple different parsers to be
|
|
21
|
+
# used at the same time...
|
|
22
|
+
|
|
23
|
+
CALLBACK_MAYBE = "CALLBACK_MAYBE"
|
|
24
|
+
SPAN_CALLBACK_MAYBE = "SPAN_CALLBACK_MAYBE"
|
|
25
|
+
MID_POSTFIX = "_settings"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ApiResult:
|
|
31
|
+
header: str
|
|
32
|
+
c: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Settings:
|
|
37
|
+
name: str
|
|
38
|
+
"""The property name to take from the settings module..."""
|
|
39
|
+
prefix: str
|
|
40
|
+
"""The Prefix of your parser"""
|
|
41
|
+
postfix: Optional[str] = None
|
|
42
|
+
"""The postfix of your parser"""
|
|
43
|
+
callbacks: dict[str, bool] = field(default_factory=dict)
|
|
44
|
+
"""These are callbacks to be later edited out into thier appropreate places"""
|
|
45
|
+
files: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
46
|
+
"""Used to sort out functions if they are to be moved to different places these
|
|
47
|
+
files will not affect the main api and can be ignored if chosen to be..."""
|
|
48
|
+
|
|
49
|
+
external_execute_func: str = field(default_factory=str, init=False)
|
|
50
|
+
"""Allows the use of an external function and it's prefix to be called
|
|
51
|
+
```c
|
|
52
|
+
|
|
53
|
+
int POSTFIX_execute(PREFIX_t* parser, const char* data, size_t len) {
|
|
54
|
+
return PREFIX_execute(parser, data, data + len);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
WARNIG! THIS SHOULD ONLY BE USED IF A POSTFIX HAS BEEN ADDED!"""
|
|
59
|
+
|
|
60
|
+
# It can be annoying to have to recall something over and over again so I added the ability to use
|
|
61
|
+
# Nodes and Code.Match classes to make up for this annoyance...
|
|
62
|
+
def move_cb(
|
|
63
|
+
self,
|
|
64
|
+
callback: Union[str, source._Match, source.Node, source.Invoke],
|
|
65
|
+
new_location: str,
|
|
66
|
+
code: Optional[str] = None,
|
|
67
|
+
):
|
|
68
|
+
"""Moves the callback chosen to another file instead of the name of your api
|
|
69
|
+
this can help you add things like custom C code, to non-api or user-interacted parts...
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# Grab real name if the callback is a Node or Match otherwise get the string for it...
|
|
73
|
+
|
|
74
|
+
name = (
|
|
75
|
+
callback.name
|
|
76
|
+
if isinstance(callback, (source.Code, source.Node))
|
|
77
|
+
else callback
|
|
78
|
+
)
|
|
79
|
+
# If not added make a new dictioray / Map if your on typescript...
|
|
80
|
+
if not self.files.get(new_location):
|
|
81
|
+
self.files[new_location] = {}
|
|
82
|
+
|
|
83
|
+
self.files[new_location][name] = code
|
|
84
|
+
|
|
85
|
+
# Remove from default callbacks to be moved to another file in C...
|
|
86
|
+
# If index fails (IndexError) the callback name doesn't exist...
|
|
87
|
+
self.callbacks.pop(name)
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def add_external_execute(self, postfix_as_struct: bool = False):
|
|
91
|
+
"""Adds an external execute function as long as a postfix variable exists otherwise
|
|
92
|
+
using this function this is not nessesary!
|
|
93
|
+
```c
|
|
94
|
+
int POSTFIX_execute(PREFIX_t* parser, const char* data, size_t len) {
|
|
95
|
+
return PREFIX_execute(parser, data, data + len);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
Parameters
|
|
99
|
+
---
|
|
100
|
+
`postfix_as_struct` use postfix struct instead of inside of POSTFIX_execute's function...
|
|
101
|
+
"""
|
|
102
|
+
if self.postfix:
|
|
103
|
+
self.external_execute_func += (
|
|
104
|
+
f"int {self.postfix}_execute({self.postfix if postfix_as_struct else self.prefix}_t* parser, const char* data, size_t len) "
|
|
105
|
+
+ "{"
|
|
106
|
+
)
|
|
107
|
+
self.external_execute_func += (
|
|
108
|
+
f"\nreturn {self.prefix}_execute(parser, data, data + len);\n" + "}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
def build_functions(self, out: list[str]):
|
|
114
|
+
# Added newline to this one to reamin consistant with llhttp if the library devs decided to
|
|
115
|
+
# implement my idea into thier projects
|
|
116
|
+
out.append("/* CALLBACKS */\n")
|
|
117
|
+
out.append("")
|
|
118
|
+
# TODO Vizonex allow custom enum names to be passed as well in the next concept...
|
|
119
|
+
for cb, is_span in self.callbacks.items():
|
|
120
|
+
out.append(
|
|
121
|
+
f"int {cb}({self.prefix}_t * s, const char* p, const char* endp) " + "{"
|
|
122
|
+
)
|
|
123
|
+
out.append(" int err;")
|
|
124
|
+
if is_span:
|
|
125
|
+
out.append(f" {SPAN_CALLBACK_MAYBE}(s, {cb} ,p, endp - p);")
|
|
126
|
+
else:
|
|
127
|
+
out.append(f" {CALLBACK_MAYBE}(s, {cb});")
|
|
128
|
+
out.append(" return err;")
|
|
129
|
+
# Close function and give some room for the next one to be placed into...
|
|
130
|
+
out.append("}")
|
|
131
|
+
out.append("")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
def buildMacros(
|
|
135
|
+
self,
|
|
136
|
+
out: list[str],
|
|
137
|
+
callback: str = CALLBACK_MAYBE,
|
|
138
|
+
span_callback: str = SPAN_CALLBACK_MAYBE,
|
|
139
|
+
):
|
|
140
|
+
"""Builds Global Macro Callbacks"""
|
|
141
|
+
# Inspired by llhttp
|
|
142
|
+
out.append(
|
|
143
|
+
f"#define {callback}(PARSER, NAME) \\"
|
|
144
|
+
)
|
|
145
|
+
out.append(
|
|
146
|
+
"do { \\"
|
|
147
|
+
)
|
|
148
|
+
out.append(
|
|
149
|
+
f" const {self.prefix}{self.name}_t* settings; \\"
|
|
150
|
+
)
|
|
151
|
+
out.append(
|
|
152
|
+
f" settings = (const {self.prefix}{self.name}_t*) (PARSER)->{self.name}; \\"
|
|
153
|
+
)
|
|
154
|
+
out.append(
|
|
155
|
+
" if (settings == NULL || settings->NAME == NULL) { \\"
|
|
156
|
+
)
|
|
157
|
+
out.append(
|
|
158
|
+
" err = 0; \\"
|
|
159
|
+
)
|
|
160
|
+
out.append(
|
|
161
|
+
" break; \\"
|
|
162
|
+
)
|
|
163
|
+
out.append(
|
|
164
|
+
" } \\"
|
|
165
|
+
)
|
|
166
|
+
out.append(
|
|
167
|
+
" err = settings->NAME((PARSER)); \\"
|
|
168
|
+
)
|
|
169
|
+
out.append("} while (0)")
|
|
170
|
+
out.append("")
|
|
171
|
+
|
|
172
|
+
out.append(
|
|
173
|
+
f"#define {span_callback}(PARSER, NAME, START, LEN) \\"
|
|
174
|
+
)
|
|
175
|
+
out.append(
|
|
176
|
+
" do { \\"
|
|
177
|
+
)
|
|
178
|
+
out.append(
|
|
179
|
+
f" const {self.prefix}{self.name}_t* settings; \\"
|
|
180
|
+
)
|
|
181
|
+
out.append(
|
|
182
|
+
f" settings = (const {self.prefix}{self.name}_t*) (PARSER)->{self.name}; \\"
|
|
183
|
+
)
|
|
184
|
+
out.append(
|
|
185
|
+
" if (settings == NULL || settings->NAME == NULL) { \\"
|
|
186
|
+
)
|
|
187
|
+
out.append(
|
|
188
|
+
" err = 0; \\"
|
|
189
|
+
)
|
|
190
|
+
out.append(
|
|
191
|
+
" break; \\"
|
|
192
|
+
)
|
|
193
|
+
out.append(
|
|
194
|
+
" } \\"
|
|
195
|
+
)
|
|
196
|
+
out.append(
|
|
197
|
+
" err = settings->NAME((PARSER), (START), (LEN)); \\"
|
|
198
|
+
)
|
|
199
|
+
# TODO: maybe allow for custom error functions to be raised right here
|
|
200
|
+
# or from or with both of them optionally? --
|
|
201
|
+
out.append(" } while (0)")
|
|
202
|
+
out.append("\n\n\n")
|
|
203
|
+
|
|
204
|
+
def build_C(self, cb: str, span_cb: str):
|
|
205
|
+
out = []
|
|
206
|
+
out.append("#include <stdlib.h>")
|
|
207
|
+
out.append("#include <stdio.h>")
|
|
208
|
+
out.append("#include <string.h>")
|
|
209
|
+
out.append("")
|
|
210
|
+
out.append(f'#include "{self.prefix}.h"')
|
|
211
|
+
out.append("")
|
|
212
|
+
self.buildMacros(out, cb, span_cb)
|
|
213
|
+
self.build_functions(out)
|
|
214
|
+
return "\n".join(out)
|
|
215
|
+
|
|
216
|
+
def build_H(self):
|
|
217
|
+
out = []
|
|
218
|
+
|
|
219
|
+
fix = self.postfix if self.postfix else self.prefix
|
|
220
|
+
|
|
221
|
+
out.append(f"typedef struct {fix}{self.name}_s {fix}{self.name}_t;")
|
|
222
|
+
out.append(
|
|
223
|
+
f"typedef int (*{fix}_data_cb)({fix}_t*, const char *at, size_t length);"
|
|
224
|
+
)
|
|
225
|
+
out.append(f"typedef int (*{fix}_cb)({fix}_t*);")
|
|
226
|
+
|
|
227
|
+
out.append(f"struct {self.prefix}{self.name}_s " + "{")
|
|
228
|
+
for cb, is_span in self.callbacks.items():
|
|
229
|
+
if is_span:
|
|
230
|
+
out.append(f" {fix}_data_cb {cb};")
|
|
231
|
+
else:
|
|
232
|
+
out.append(f" {fix}_cb {cb};")
|
|
233
|
+
out.append("};")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class Disassembler:
|
|
237
|
+
"""Takes apart LLParse's calls and sorts the data out to help make
|
|
238
|
+
different things such as a settings api moudle, Cython pxd file
|
|
239
|
+
and a Typescript Writer, and a Json model of what's going on...
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
def __init__(self, builder: LLParse) -> None:
|
|
243
|
+
self.invokes: set[source.Invoke] = set()
|
|
244
|
+
self.nodes: dict[str, source.Node] = {}
|
|
245
|
+
"""Used to create a small dictionary of all avalible nodes to thier corresponding names.
|
|
246
|
+
this also includes it's edges used and seen to create a json file if chosen."""
|
|
247
|
+
self.empty_apis: dict[str, bool] = {}
|
|
248
|
+
"""Carries some of the main api calls to make in a settings module...
|
|
249
|
+
These carry thier own boolean to see weather or not the callback is a `Span` (True) ,
|
|
250
|
+
`Match` (False)..."""
|
|
251
|
+
self.prefix = builder.prefix
|
|
252
|
+
self.properties = builder.privProperties
|
|
253
|
+
"""Carries important property values"""
|
|
254
|
+
|
|
255
|
+
def disassemble(self, root: source.Node):
|
|
256
|
+
"""Takes root apart and enumerates through all the major parts
|
|
257
|
+
returns itself as a convenience measure..."""
|
|
258
|
+
reach = source.Reachability()
|
|
259
|
+
roots = reach.build(root)
|
|
260
|
+
# Get all span's names and objects themselves...
|
|
261
|
+
# Turn span_names into a set
|
|
262
|
+
self.nodes = {n.name: n for n in roots}
|
|
263
|
+
# print(self.nodes)
|
|
264
|
+
span_names = list(
|
|
265
|
+
sorted(
|
|
266
|
+
{
|
|
267
|
+
n.span.callback.name
|
|
268
|
+
for n in roots
|
|
269
|
+
if isinstance(n, (source.SpanStart, source.SpanEnd))
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
# span_starts = {n.span.callback.name:n for n in roots if isinstance(n,source.SpanStart)}
|
|
274
|
+
|
|
275
|
+
self.empty_apis.update({sn: True for sn in span_names})
|
|
276
|
+
|
|
277
|
+
invokes = [n for n in roots if isinstance(n, source.Invoke)]
|
|
278
|
+
|
|
279
|
+
for i in invokes:
|
|
280
|
+
# I have Match Underscored because in my version of the library there's two of them in the same file.
|
|
281
|
+
# The one with the underscore is for use with Invoke...
|
|
282
|
+
if isinstance(i.code, source._Match):
|
|
283
|
+
self.empty_apis[i.code.name] = False
|
|
284
|
+
|
|
285
|
+
return self
|
llparse/spanalloc.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from .pybuilder.main_code import Node, Reachability, Span, SpanEnd, SpanStart
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DeadLoop(Exception):
|
|
8
|
+
"""Thrown when this type of loop is detected during complation
|
|
9
|
+
```c
|
|
10
|
+
switch(*p){
|
|
11
|
+
case dead_loop:
|
|
12
|
+
dead_loop : {
|
|
13
|
+
goto dead_loop;
|
|
14
|
+
} /* Rest in Peace Computer x_x */
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
```"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
SpanSet = set[Span]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _id(node: Union[SpanStart, SpanEnd]):
|
|
24
|
+
return node.span
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ISpanActiveInfo:
|
|
29
|
+
active: dict[Node, SpanSet] = field(default_factory=dict)
|
|
30
|
+
spans: list[Span] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
SpanOverlap = dict[Node, SpanSet]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ISpanAllocatorResult:
|
|
38
|
+
colors: dict[Span, int] = field(default_factory=dict)
|
|
39
|
+
concurrency: list[list[Span]] = field(default_factory=list)
|
|
40
|
+
max: int = field(default_factory=int)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SpanAllocator:
|
|
44
|
+
def __init__(self) -> None:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
def allocate(self, root: Node):
|
|
48
|
+
nodes = Reachability.build(root)
|
|
49
|
+
info = self.computeActive(nodes)
|
|
50
|
+
|
|
51
|
+
self.check(info)
|
|
52
|
+
overlap = self.computeOverlap(info)
|
|
53
|
+
return self.color(info.spans, overlap)
|
|
54
|
+
|
|
55
|
+
def check(self, info: ISpanActiveInfo):
|
|
56
|
+
for node, spans in info.active.items():
|
|
57
|
+
for edge in node.getAllEdges():
|
|
58
|
+
if isinstance(edge.node, SpanStart):
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Skip terminal nodes
|
|
62
|
+
# print(len(edge.node.getAllEdges()))
|
|
63
|
+
# print(info.active)
|
|
64
|
+
if len(edge.node.getAllEdges()) == 0:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# assert node.name != edge.node.name
|
|
68
|
+
# print("checking edge from %s to %s" % (node.name,edge.node.name))
|
|
69
|
+
# check edge
|
|
70
|
+
|
|
71
|
+
edgeSpans: set[Span] = info.active[edge.node]
|
|
72
|
+
# print("SPAN:%s NODE:%s" % (edgeSpans,edge.node.__dict__))
|
|
73
|
+
for subSpan in edgeSpans:
|
|
74
|
+
if subSpan not in spans:
|
|
75
|
+
raise AssertionError(
|
|
76
|
+
f'Unmatched span end for "{subSpan.callback.name}"'
|
|
77
|
+
/ f'at "{edge.node.name}", coming from "{node.name}"'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if isinstance(edge.node, SpanEnd):
|
|
81
|
+
span = _id(edge.node)
|
|
82
|
+
if span not in spans:
|
|
83
|
+
raise AssertionError(
|
|
84
|
+
f'Unmatched span end for "{span.callback.name}"'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def computeActive(self, nodes: list[Node]):
|
|
88
|
+
activeMap: dict[Node, SpanSet] = dict()
|
|
89
|
+
for node in nodes:
|
|
90
|
+
activeMap[node] = set()
|
|
91
|
+
|
|
92
|
+
queue = set(nodes)
|
|
93
|
+
spans: SpanSet = set()
|
|
94
|
+
# This fixes an issue when using a for loop which unlike in typescript
|
|
95
|
+
# we cannot remove items when in a for-loop in python this also ensures
|
|
96
|
+
# that all spans are visited.
|
|
97
|
+
while queue:
|
|
98
|
+
node = queue.pop()
|
|
99
|
+
active = activeMap[node]
|
|
100
|
+
if isinstance(node, SpanStart):
|
|
101
|
+
span = _id(node)
|
|
102
|
+
spans.add(span)
|
|
103
|
+
active.add(span)
|
|
104
|
+
|
|
105
|
+
for span in active:
|
|
106
|
+
if isinstance(node, SpanEnd) and span == _id(node):
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
for edge in node.getAllEdges():
|
|
110
|
+
edgeNode = edge.node
|
|
111
|
+
|
|
112
|
+
if isinstance(edgeNode, SpanStart):
|
|
113
|
+
if _id(edgeNode) == span:
|
|
114
|
+
raise DeadLoop(
|
|
115
|
+
f'Detected loop in span {span.callback.name} at "{node.name}"'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
edgeActive = activeMap[edgeNode]
|
|
119
|
+
if span in edgeActive:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
edgeActive.add(span)
|
|
123
|
+
queue.add(edgeNode)
|
|
124
|
+
|
|
125
|
+
return ISpanActiveInfo(active=activeMap, spans=list(spans))
|
|
126
|
+
|
|
127
|
+
def computeOverlap(self, info: ISpanActiveInfo):
|
|
128
|
+
active = info.active
|
|
129
|
+
|
|
130
|
+
overlap: dict[Span, set[Span]] = {span: set() for span in info.spans}
|
|
131
|
+
for _, spans in active.items():
|
|
132
|
+
for one in spans:
|
|
133
|
+
for other in spans:
|
|
134
|
+
if other != one:
|
|
135
|
+
overlap[one].add(other)
|
|
136
|
+
return overlap
|
|
137
|
+
|
|
138
|
+
def _allocate(self, span: Span):
|
|
139
|
+
if self._colors.get(span):
|
|
140
|
+
return self._colors[span]
|
|
141
|
+
|
|
142
|
+
overlap = self._overlapMap[span]
|
|
143
|
+
|
|
144
|
+
used: set[int] = set()
|
|
145
|
+
for subSpan in overlap:
|
|
146
|
+
if self._colors.get(subSpan):
|
|
147
|
+
used.add(self._colors.get(subSpan))
|
|
148
|
+
i = 0
|
|
149
|
+
while i in used:
|
|
150
|
+
i += 1
|
|
151
|
+
|
|
152
|
+
self._mx = max(self._mx, i)
|
|
153
|
+
self._colors[span] = i
|
|
154
|
+
return i
|
|
155
|
+
|
|
156
|
+
def color(self, spans: list[Span], overlapDict: SpanOverlap):
|
|
157
|
+
# Used _max instead of max because max() is an api called function needed in a bit...
|
|
158
|
+
self._mx = -1
|
|
159
|
+
self._colors: dict[Span, int] = {}
|
|
160
|
+
|
|
161
|
+
self._overlapMap = overlapDict
|
|
162
|
+
|
|
163
|
+
Map = {span: self._allocate(span) for span in spans}
|
|
164
|
+
|
|
165
|
+
concurrency = list()
|
|
166
|
+
for _ in range(self._mx + 1):
|
|
167
|
+
# NOTE : concurrency[i] = [] doesn't work but this does :P
|
|
168
|
+
concurrency.append([])
|
|
169
|
+
|
|
170
|
+
for s in spans:
|
|
171
|
+
concurrency[self._allocate(s)].append(s)
|
|
172
|
+
|
|
173
|
+
return ISpanAllocatorResult(Map, concurrency, self._mx)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# TODO (Vizonex) Use Indutny's Mini Http parser to help with testing ours to verify that ours is correct...
|
llparse/test.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from .C_compiler import Compilation, ICompilerOptions
|
|
2
|
+
from .frontend import Frontend
|
|
3
|
+
from .llparse import LLParse
|
|
4
|
+
from .pybuilder import Builder
|
|
5
|
+
|
|
6
|
+
# TODO: Remove and make pytest for it.
|
|
7
|
+
|
|
8
|
+
def smaller_test():
|
|
9
|
+
# from compilator import Compilation, ICompilerOptions
|
|
10
|
+
p = Builder()
|
|
11
|
+
simple = p.node("simple")
|
|
12
|
+
complete = p.invoke(
|
|
13
|
+
p.code.match("On_Complete"), {0: simple}, p.error(1, "On_Complete FAILED!")
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
simple.peek("\n", complete).skipTo(simple)
|
|
17
|
+
|
|
18
|
+
front = Frontend("http_parser")
|
|
19
|
+
f = front.compile(simple, properties=p.properties)
|
|
20
|
+
comp = Compilation(
|
|
21
|
+
"http_parser", f.properties, f.resumptionTargets, ICompilerOptions(None, None)
|
|
22
|
+
)
|
|
23
|
+
# for r in comp.resumptionTargets:
|
|
24
|
+
# print(r)
|
|
25
|
+
out = []
|
|
26
|
+
root = comp.unwrapNode(f.root)
|
|
27
|
+
root.build(comp)
|
|
28
|
+
comp.reserveSpans(f.spans)
|
|
29
|
+
comp.buildGlobals(out)
|
|
30
|
+
comp.buildResumptionStates(out)
|
|
31
|
+
print(comp.stateDict)
|
|
32
|
+
# print("\n".join(out))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test1():
|
|
36
|
+
# I will be using indutny's mini http parser to demonstrate as it's
|
|
37
|
+
# medium sized and uses all major tricks , bells and whistles...
|
|
38
|
+
|
|
39
|
+
p = Builder()
|
|
40
|
+
method = p.node("method")
|
|
41
|
+
beforeUrl = p.node("before_url")
|
|
42
|
+
urlSpan = p.span(p.code.span("on_url"))
|
|
43
|
+
url = p.node("url")
|
|
44
|
+
http = p.node("http")
|
|
45
|
+
|
|
46
|
+
# print(sliced)(method)
|
|
47
|
+
|
|
48
|
+
# // Add custom uint8_t property to the state
|
|
49
|
+
p.property("i8", "method")
|
|
50
|
+
|
|
51
|
+
# Store method inside a custom property
|
|
52
|
+
onMethod = p.invoke(p.code.store("method"), beforeUrl)
|
|
53
|
+
|
|
54
|
+
# Invoke custom C function
|
|
55
|
+
complete = p.invoke(
|
|
56
|
+
p.code.match("on_complete"),
|
|
57
|
+
{
|
|
58
|
+
# Restart
|
|
59
|
+
0: method
|
|
60
|
+
},
|
|
61
|
+
p.error(4, "`on_complete` error"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
method.select(
|
|
65
|
+
{
|
|
66
|
+
"HEAD": 0,
|
|
67
|
+
"GET": 1,
|
|
68
|
+
"POST": 2,
|
|
69
|
+
"PUT": 3,
|
|
70
|
+
"DELETE": 4,
|
|
71
|
+
"OPTIONS": 5,
|
|
72
|
+
"CONNECT": 6,
|
|
73
|
+
"TRACE": 7,
|
|
74
|
+
"PATCH": 8,
|
|
75
|
+
},
|
|
76
|
+
onMethod,
|
|
77
|
+
).otherwise(p.error(5, "Expected method"))
|
|
78
|
+
|
|
79
|
+
beforeUrl.match(" ", beforeUrl).otherwise(urlSpan.start(url))
|
|
80
|
+
|
|
81
|
+
url.peek(" ", urlSpan.end(http)).skipTo(url)
|
|
82
|
+
|
|
83
|
+
http.match(" HTTP/1.1\r\n\r\n", complete).otherwise(
|
|
84
|
+
p.error(6, "Expected HTTP/1.1 and two newlines")
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
front = Frontend("http_parser")
|
|
88
|
+
f = front.compile(
|
|
89
|
+
method,
|
|
90
|
+
properties=p.properties,
|
|
91
|
+
)
|
|
92
|
+
# r = f.root.ref.id.name
|
|
93
|
+
# NOTE Check that root was addded , this can be a problem laster down the line if it isn't a resumption target...
|
|
94
|
+
assert f.root in f.resumptionTargets
|
|
95
|
+
comp = Compilation(
|
|
96
|
+
"http_parser", p.properties(), f.resumptionTargets, ICompilerOptions()
|
|
97
|
+
)
|
|
98
|
+
root = comp.unwrapNode(f.root)
|
|
99
|
+
root.build(comp)
|
|
100
|
+
|
|
101
|
+
assert "s_n_http_parser__n_method" in comp.resumptionTargets
|
|
102
|
+
out = []
|
|
103
|
+
comp.reserveSpans(f.spans)
|
|
104
|
+
comp.buildGlobals(out)
|
|
105
|
+
out.append("")
|
|
106
|
+
out.append("/*--RESUMPTION STATES--*/")
|
|
107
|
+
comp.buildResumptionStates(out)
|
|
108
|
+
out.append("/*--INTERNAL STATES--*/")
|
|
109
|
+
comp.buildInternalStates(out)
|
|
110
|
+
# for k , v in comp.stateDict.items():
|
|
111
|
+
# print(k)
|
|
112
|
+
print("\n".join(out))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test2():
|
|
116
|
+
from llparse import LLParse
|
|
117
|
+
|
|
118
|
+
p = LLParse("http_parser")
|
|
119
|
+
method = p.node("method")
|
|
120
|
+
beforeUrl = p.node("before_url")
|
|
121
|
+
urlSpan = p.span(p.code.span("on_url"))
|
|
122
|
+
url = p.node("url")
|
|
123
|
+
http = p.node("http")
|
|
124
|
+
|
|
125
|
+
# Add custom uint8_t property to the state
|
|
126
|
+
p.property("i8", "method")
|
|
127
|
+
|
|
128
|
+
# Store method inside a custom property
|
|
129
|
+
onMethod = p.invoke(p.code.store("method"), beforeUrl)
|
|
130
|
+
|
|
131
|
+
# Invoke custom C function
|
|
132
|
+
complete = p.invoke(
|
|
133
|
+
p.code.match("on_complete"),
|
|
134
|
+
{
|
|
135
|
+
# Restart
|
|
136
|
+
0: method
|
|
137
|
+
},
|
|
138
|
+
p.error(4, "`on_complete` error"),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
method.select(
|
|
142
|
+
{
|
|
143
|
+
"HEAD": 0,
|
|
144
|
+
"GET": 1,
|
|
145
|
+
"POST": 2,
|
|
146
|
+
"PUT": 3,
|
|
147
|
+
"DELETE": 4,
|
|
148
|
+
"OPTIONS": 5,
|
|
149
|
+
"CONNECT": 6,
|
|
150
|
+
"TRACE": 7,
|
|
151
|
+
"PATCH": 8,
|
|
152
|
+
},
|
|
153
|
+
onMethod,
|
|
154
|
+
).otherwise(p.error(5, "Expected method"))
|
|
155
|
+
|
|
156
|
+
beforeUrl.match(" ", beforeUrl).otherwise(urlSpan.start(url))
|
|
157
|
+
|
|
158
|
+
url.peek(" ", urlSpan.end(http)).skipTo(url)
|
|
159
|
+
|
|
160
|
+
http.match(" HTTP/1.1\r\n\r\n", complete).otherwise(
|
|
161
|
+
p.error(6, "Expected HTTP/1.1 and two newlines")
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
c = p.build(method)
|
|
165
|
+
print(c.c)
|
|
166
|
+
open("http_parser.c", "w").write(c.c)
|
|
167
|
+
open("http_parser.h", "w").write(c.header)
|
|
168
|
+
# # sp_alloc = SpanAllocator().allocate(method)
|
|
169
|
+
|
|
170
|
+
# TODO Validate Moving parts as they should be compiled correctly...
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test3():
|
|
174
|
+
"""Moving Part Validations..."""
|
|
175
|
+
# A~Sample~Of~Dual Spans...
|
|
176
|
+
p = LLParse("dual_spans")
|
|
177
|
+
|
|
178
|
+
span_a = p.span(p.code.span("on_a"))
|
|
179
|
+
span_b = p.span(p.code.span("on_b"))
|
|
180
|
+
|
|
181
|
+
a = p.node("a_node")
|
|
182
|
+
b = p.node("b_node")
|
|
183
|
+
|
|
184
|
+
start = p.node("start")
|
|
185
|
+
start.otherwise(span_a.start(a))
|
|
186
|
+
|
|
187
|
+
a.peek("~", span_a.end().skipTo(span_b.start(b))).skipTo(a)
|
|
188
|
+
b.peek("~", span_b.end().skipTo(span_a.start(a))).skipTo(b)
|
|
189
|
+
|
|
190
|
+
compiled = p.build(start)
|
|
191
|
+
print(compiled.c)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test4():
|
|
195
|
+
"""Same Dual Spans but with flags..."""
|
|
196
|
+
# A~Sample~Of~Dual Spans...
|
|
197
|
+
|
|
198
|
+
p = LLParse("dual_spans")
|
|
199
|
+
|
|
200
|
+
# Flag
|
|
201
|
+
p.property("i8", "flag")
|
|
202
|
+
|
|
203
|
+
span_a = p.span(p.code.span("on_a"))
|
|
204
|
+
span_b = p.span(p.code.span("on_b"))
|
|
205
|
+
|
|
206
|
+
a = p.node("a_node")
|
|
207
|
+
b = p.node("b_node")
|
|
208
|
+
|
|
209
|
+
# Invoke Updates to Flag
|
|
210
|
+
# 1 is a , 2 is b
|
|
211
|
+
|
|
212
|
+
update_a = p.invoke(p.code.update("flag", 1), span_a.start(a))
|
|
213
|
+
update_b = p.invoke(p.code.update("flag", 2), span_b.start(b))
|
|
214
|
+
start = p.node("start")
|
|
215
|
+
start.otherwise(update_a)
|
|
216
|
+
|
|
217
|
+
a.peek("~", span_a.end().skipTo(update_a)).skipTo(a)
|
|
218
|
+
b.peek("~", span_b.end().skipTo(update_b)).skipTo(b)
|
|
219
|
+
|
|
220
|
+
compiled = p.build(start)
|
|
221
|
+
print(compiled.c)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# TODO Try to Throw more complicated and advanced things at pyllparse to try and solve
|
|
225
|
+
# This will make the code sturdier and more mature as time goes on
|
|
226
|
+
# Make a Test Bag on github for all of these tests to go into incase a single file gets too big
|
|
227
|
+
|
|
228
|
+
# I encourage anyone to find valid compiled llparse sequences and then translate them over to
|
|
229
|
+
# python to testrun , I will try to make a validator tool whenever I can get to it - Vizonex
|
|
230
|
+
|
|
231
|
+
if __name__ == "__main__":
|
|
232
|
+
test2()
|