Skip to content

Commit 452375b

Browse files
committed
fix table lookup generation
1 parent 4a4400c commit 452375b

File tree

8 files changed

+107
-82
lines changed

8 files changed

+107
-82
lines changed

llparse/compilator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ def doBuild(self, out: list[str]):
572572
else:
573573
ch = f"'{chr(e.key)}'"
574574

575-
out.append(f" case {ch}:" + "{")
575+
out.append(f" case {ch}: " + "{")
576576
tmp: list[str] = []
577577

578578
# For now debug everything....

llparse/debug.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ def getAllNodes(root: Node):
99

1010
while queue:
1111
node = queue.pop()
12-
print(node.name)
13-
if node.name == "nmethods":
14-
print(node.getEdges())
1512
if edges := node.getEdges():
1613
for slot in edges:
1714
if slot.node in nodes:

llparse/frontend.py

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
from .pyfront.nodes import ITableEdge
1313
from .pyfront.peephole import Peephole
1414
from .spanalloc import SpanAllocator
15-
from .trie import Trie, TrieEmpty, TrieNode, TrieSequence, TrieSingle
15+
from .trie import Trie, TrieEmpty, TrieNode, TrieSequence, TrieSingle, ITrieSingleChild
1616

1717
DEFAULT_MIN_TABLE_SIZE = 32
1818
DEFAULT_MAX_TABLE_WIDTH = 4
1919

20+
from logging import getLogger
21+
22+
log = getLogger("llparse.frontend")
23+
2024

2125
WrappedNode = IWrap[_frontend.node.Node]
2226
WrappedCode = IWrap[_frontend.code.Code]
@@ -166,7 +170,7 @@ def translateMatch(self, node: source.code.Match) -> list[WrappedNode]:
166170
trieNode = trie.build(list(node))
167171

168172
if not trieNode:
169-
# print("[DEBUG]", "TrieNode was nonexistant")
173+
log.debug("TrieNode was nonexistant")
170174
return self.implementation.node.Empty(
171175
_frontend.node.Empty(self.Id.id(node.name))
172176
)
@@ -178,7 +182,7 @@ def translateMatch(self, node: source.code.Match) -> list[WrappedNode]:
178182

179183
return children
180184

181-
def registerNode(self, node: WrappedNode):
185+
def registerNode(self, node: WrappedNode) -> None:
182186
# NOTE NO Implementations required here since this is python!
183187
if isinstance(
184188
node.ref,
@@ -287,7 +291,6 @@ def ID():
287291

288292
if isinstance(single.ref, _frontend.node.Invoke):
289293
for edge in node:
290-
# print(edge.key)
291294
single.ref.addEdge(
292295
ord(edge.key) if isinstance(edge.key, str) else edge.key,
293296
self.translate(edge.node),
@@ -304,52 +307,48 @@ def maybeTableLookup(
304307
return None
305308

306309
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
310+
311+
def check_child(child: ITrieSingleChild):
312+
nonlocal targets
313+
if not isinstance(child.node, TrieEmpty):
314+
log.debug(
315+
'non-leaf trie child of "%s" prevents table allocation' % node.name
316+
)
317+
return False
318+
empty = child.node
319+
if empty.value is not None:
320+
log.debug(
321+
'value passing trie leaf of "%s" prevents table allocation'
322+
% node.name
323+
)
324+
return False
324325

325326
target = empty.node
326-
if not targets.get(target):
327+
if target not in targets:
327328
targets[target] = ITableLookupTarget(
328329
keys=[child.key], noAdvance=child.noAdvance, trie=empty
329330
)
330-
bailout = True
331-
break
331+
return True
332332

333333
existing = targets[target]
334-
335334
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-
335+
log.debug(
336+
f'noAdvance mismatch in a trie leaf of "{node.name}" prevents '
337+
"table allocation"
338+
)
339+
return False
340340
existing.keys.append(child.key)
341+
return True
341342

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:
343+
if not all([check_child(child) for child in trie.children]):
348344
return
349345

350346
# 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)
347+
if len(targets) >= (1 << self.options["maxTableElemWidth"]):
348+
log.debug(
349+
'too many different trie targets of "%s" for a table allocation'
350+
% node.name
351+
)
353352
return
354353

355354
table = self.implementation.node.TableLookup(
@@ -358,11 +357,11 @@ def maybeTableLookup(
358357
children.append(table)
359358

360359
# Break Loop
361-
if self.Map.get(node):
360+
if not self.Map.get(node):
362361
self.Map[node] = table
363362

364363
for target in targets.values():
365-
_next = self.translateTrie(node, target, children)
364+
_next = self.translateTrie(node, target.trie, children)
366365
table.ref.addEdge(
367366
ITableEdge(keys=target.keys, noAdvance=target.noAdvance, node=_next)
368367
)
@@ -408,9 +407,7 @@ def translateSingle(
408407
self, node: source.code.Match, trie: TrieSingle, children: MatchChildren
409408
):
410409
# Check if Tablelookup could be a valid option to Optimze our code up...
411-
maybeTable = self.maybeTableLookup(node, trie, children)
412-
413-
if maybeTable:
410+
if maybeTable := self.maybeTableLookup(node, trie, children):
414411
return maybeTable
415412

416413
single = self.implementation.node.Single(
@@ -432,8 +429,7 @@ def translateSingle(
432429
value=child.node.value if isinstance(child.node, TrieEmpty) else None,
433430
)
434431

435-
otherwise = trie.otherwise
436-
if otherwise:
432+
if otherwise := trie.otherwise:
437433
single.ref.setOtherwise(
438434
self.translateTrie(node, otherwise, children), True, otherwise.value
439435
)
@@ -442,8 +438,9 @@ def translateSingle(
442438
def translateSpanCode(self, code: source.code._Span):
443439
return self.translateCode(code)
444440

445-
# TODO Vizonex Maybe better typehining can be used in this function alone....
446-
def translateCode(self, code: source.code.Code):
441+
def translateCode(
442+
self, code: source.code.Code
443+
):
447444
"""Translates Builder Classes to Frontend Classes..."""
448445

449446
prefixed = self.codeId.id(code.name).name
@@ -499,9 +496,8 @@ def translateCode(self, code: source.code.Code):
499496
else:
500497
raise Exception(f'UnSupported code:"{code.name}" type: "{type(code)}"')
501498

502-
if self.codeCache.get(res.ref.cacheKey):
503-
return self.codeCache[res.ref.cacheKey]
504-
499+
if _res := self.codeCache.get(res.ref.cacheKey):
500+
return _res
505501
self.codeCache[res.ref.cacheKey] = res
506502
return res
507503

llparse/llparse.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,20 @@ def compile(
6767
properties: list[source.Property],
6868
header_name: Optional[str] = None,
6969
Impl: Optional[IImplementation] = IImplementation(),
70+
override_llparse_name: bool = False
7071
):
7172
"""Creates the C and header file..."""
7273
info = self.to_frontend(root, properties, Impl)
7374
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+
7482
return CompilerResult(
75-
CCompiler(header_name, self.debug).compile(info), hb.build()
83+
cdata , hb.build()
7684
)
7785

7886

@@ -122,6 +130,7 @@ def build(
122130
maxTableElemWidth: Optional[int] = None,
123131
minTableSize: Optional[int] = None,
124132
header_name: Optional[str] = None,
133+
override_llparse_name:bool = False
125134
):
126135
"""Builds Graph and then compiles the data into C code , returns with the header and C file inside of a Dataclass"""
127136

@@ -133,7 +142,8 @@ def build(
133142
minTableSize if minTableSize else DEFAULT_MIN_TABLE_SIZE,
134143
)
135144

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+
137147

138148
def to_frontend(
139149
self,

llparse/pyfront/nodes.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass, field
2-
from typing import Any, Optional
2+
from typing import Any, Optional, Callable
33

44
from ..pyfront.front import Code, IWrap, Span, SpanField
55
from ..pyfront.transform import Transform
@@ -8,7 +8,9 @@
88
class Slot:
99
# ONLY NODE SHOULD BE ALLOWED TO BE SEEN
1010

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:
1214
self.privNode = node
1315
"""Same as calling it from get and setting the value etc..."""
1416
self.privUpdate = value
@@ -19,12 +21,17 @@ def __init__(self, node: IWrap["Node"], value: Any) -> None:
1921
# I spent 4 hours trying to figure this how this could be implemented
2022
# so this is my only ideal sloution
2123
def __hash__(self) -> int:
22-
return hash(self.privUpdate.ref.id.name)
24+
return hash(self.privNode.ref.id.name)
2325

2426
@property
2527
def node(self):
2628
return self.privNode
2729

30+
@node.setter
31+
def node(self, value: IWrap["Node"]):
32+
self.privNode = value
33+
self.privUpdate(value)
34+
2835

2936
@dataclass(unsafe_hash=True)
3037
class IUniqueName:
@@ -121,9 +128,6 @@ def buildSlots(self):
121128

122129

123130
class Empty(Node):
124-
# def __init__(self, id: IUniqueName) -> None:
125-
# super().__init__(id)
126-
127131
def __hash__(self):
128132
return hash(self.id)
129133

@@ -237,16 +241,7 @@ def addEdge(self, edge: ITableEdge):
237241
self.privEdges.append(edge)
238242

239243
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"))
244+
for e in self.privEdges:
245+
yield Slot(e.node, lambda value: setattr(e, "node", value))
246+
yield from super().buildSlots()
250247

251-
# node2 = Invoke(Ident.id("Check_Flag"),IsEqual("Flag","Check_Flag",0))
252-
# node2.setOtherwise(IWrap(node),True,0)

llparse/pyfront/peephole.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Peephole:
1111
def optimize(self, root: WrapNode, nodes: WrapList):
1212
changed = set(nodes)
1313

14-
while len(changed) != 0:
14+
while changed:
1515
previous = changed
1616
changed = set()
1717

llparse/trie.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def build(self, edges: list[Edge]):
5858
internalEdges: list[IEdge] = []
5959

6060
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
6262
internalEdges.append(
6363
IEdge(
6464
key=key.encode("utf-8") if isinstance(key, str) else key,
@@ -73,8 +73,8 @@ def build(self, edges: list[Edge]):
7373
def level(self, edges: list[IEdge], path: list[bytes] = []):
7474
first = edges[0].key
7575
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):
7878
return TrieEmpty(edges[0].node, edges[0].value)
7979

8080
i = 0
@@ -112,17 +112,17 @@ def single(self, edges: list[IEdge], path: list[bytes]):
112112
+ (b", ".join(path).decode("utf-8"))
113113
+ "]"
114114
)
115-
115+
116116
keys: dict[int, list[IEdge]] = {}
117117
otherwise = None
118118
for edge in edges:
119-
if not len(edge.key):
119+
if not edge.key:
120120
otherwise = TrieEmpty(edge.node, edge.value)
121121
continue
122122

123123
key = edge.key[0]
124124

125-
if keys.get(key):
125+
if key in keys:
126126
keys[key].append(edge)
127127
else:
128128
keys[key] = [edge]
@@ -146,7 +146,9 @@ def single(self, edges: list[IEdge], path: list[bytes]):
146146
+ "]"
147147
)
148148
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+
)
151153

152154
return TrieSingle(children, otherwise)

tests/test_frontend.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+

0 commit comments

Comments
 (0)