Skip to content

Commit 9ce95ce

Browse files
committed
update ometa.tube.trampolinedparser
1 parent 0651094 commit 9ce95ce

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

ometa/test/test_tube.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import absolute_import
2+
3+
from twisted.trial import unittest
4+
from twisted.python.compat import iterbytes
5+
6+
7+
from ometa.grammar import OMeta
8+
from ometa.tube import TrampolinedParser
9+
10+
11+
class TrampolinedReceiver():
12+
"""
13+
Receive and store the passed in data.
14+
"""
15+
def __init__(self):
16+
self.received = []
17+
18+
def receive(self, data):
19+
self.received.append(data)
20+
21+
22+
class TrampolinedParserTestCase(unittest.SynchronousTestCase):
23+
"""
24+
Tests for L{ometa.tube.TrampolinedParser}
25+
"""
26+
27+
def _parseGrammar(self, grammar, name="Grammar"):
28+
return OMeta(grammar).parseGrammar(name)
29+
30+
def setUp(self):
31+
_grammar = r"""
32+
delimiter = '\r\n'
33+
initial = <(~delimiter anything)*>:val delimiter -> receiver.receive(val)
34+
"""
35+
self.grammar = self._parseGrammar(_grammar)
36+
37+
def test_dataNotFullyReceived(self):
38+
"""
39+
Since the initial rule inside the grammar is not matched, the receiver
40+
shouldn't receive any byte.
41+
"""
42+
receiver = TrampolinedReceiver()
43+
trampolinedParser = TrampolinedParser(self.grammar, receiver, {})
44+
buf = b'foobarandnotreachdelimiter'
45+
for c in iterbytes(buf):
46+
trampolinedParser.receive(c)
47+
self.assertEqual(receiver.received, [])
48+
49+
50+
def test_dataFullyReceived(self):
51+
"""
52+
The receiver should receive the data according to the grammar.
53+
"""
54+
receiver = TrampolinedReceiver()
55+
trampolinedParser = TrampolinedParser(self.grammar, receiver, {})
56+
buf = b'\r\n'.join((b'foo', b'bar', b'foo', b'bar'))
57+
for c in iterbytes(buf):
58+
trampolinedParser.receive(c)
59+
self.assertEqual(receiver.received, [b'foo', b'bar', b'foo'])
60+
trampolinedParser.receive('\r\n')
61+
self.assertEqual(receiver.received, [b'foo', b'bar', b'foo', b'bar'])
62+
63+
64+
def test_bindings(self):
65+
"""
66+
The passed-in bindings should be accessible inside the grammar.
67+
"""
68+
receiver = TrampolinedReceiver()
69+
grammar = r"""
70+
initial = digit:d (-> int(d)+SMALL_INT):val -> receiver.receive(val)
71+
"""
72+
bindings = {'SMALL_INT': 3}
73+
TrampolinedParser(self._parseGrammar(grammar), receiver, bindings).receive('0')
74+
self.assertEqual(receiver.received, [3])

ometa/tube.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from ometa.interp import TrampolinedGrammarInterpreter, _feed_me
2+
3+
class TrampolinedParser:
4+
"""
5+
A parser that incrementally parses incoming data.
6+
"""
7+
def __init__(self, grammar, receiver, bindings):
8+
"""
9+
Initializes the parser.
10+
11+
@param grammar: The grammar used to parse the incoming data.
12+
@param receiver: Responsible for logic operation on the parsed data.
13+
Typically, the logic operation will be invoked inside the grammar,
14+
e.g., rule = expr1 expr2 (-> receiver.doSomeStuff())
15+
@param bindings: The namespace that can be accessed inside the grammar.
16+
"""
17+
self.grammar = grammar
18+
self.bindings = dict(bindings)
19+
self.bindings['receiver'] = self.receiver = receiver
20+
self._setupInterp()
21+
22+
23+
def _setupInterp(self):
24+
"""
25+
Resets the parser. The parser will begin parsing with the rule named
26+
'initial'.
27+
"""
28+
self._interp = TrampolinedGrammarInterpreter(
29+
grammar=self.grammar, ruleName='initial', callback=None,
30+
globals=self.bindings)
31+
32+
33+
def receive(self, data):
34+
"""
35+
Receive the incoming data and begin parsing. The parser will parse the
36+
data incrementally according to the 'initial' rule in the grammar.
37+
38+
@param data: The raw data received.
39+
"""
40+
while data:
41+
try:
42+
status = self._interp.receive(data)
43+
except Exception as e:
44+
# maybe we should raise it?
45+
raise e
46+
else:
47+
if status is _feed_me:
48+
return
49+
data = ''.join(self._interp.input.data[self._interp.input.position:])
50+
self._setupInterp()

0 commit comments

Comments
 (0)