1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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 91 92 93 94 95 96 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
import json
import tempfile
import unittest
from pathlib import Path
import tatsu
from tatsu.parser import EBNFBuffer
from tatsu.util import asjson, eval_escapes, trim
class MockIncludeBuffer(EBNFBuffer):
def get_include(self, source, filename):
return f'\nINCLUDED "{filename}"\n', filename
class ParsingTests(unittest.TestCase):
def test_include(self):
text = """\
first
#include :: "something"
last\
"""
buf = MockIncludeBuffer(trim(text))
self.assertEqual('first\n\nINCLUDED "something"\nlast', buf.text)
def test_multiple_include(self):
text = """\
first
#include :: "something"
#include :: "anotherthing"
last\
"""
buf = MockIncludeBuffer(trim(text))
self.assertEqual(
'first\n\nINCLUDED "something"\n\nINCLUDED "anotherthing"\nlast',
buf.text,
)
def test_real_include(self):
_, include_a = tempfile.mkstemp(suffix='.ebnf', prefix='include_')
_, include_b = tempfile.mkstemp(suffix='.ebnf', prefix='include_')
grammar = """\
#include :: "{include_a}"
#include :: "{include_b}"
start = a b;"""
with Path(include_a).open('w') as fh:
fh.write("a = 'inclusion';")
with Path(include_b).open('w') as fh:
fh.write("b = 'works';")
model = tatsu.compile(
grammar=grammar.format(include_a=include_a, include_b=include_b),
)
self.assertIsNotNone(model)
def test_escape_sequences(self):
self.assertEqual('\n', eval_escapes(r'\n'))
self.assertEqual(
'this \xeds a test', eval_escapes(r'this \xeds a test'),
)
self.assertEqual('this ís a test', eval_escapes(r'this \xeds a test'))
self.assertEqual('\nañez', eval_escapes(r'\na\xf1ez'))
self.assertEqual('\nañez', eval_escapes(r'\nañez'))
def test_start(self):
grammar = """
@@grammar :: Test
true = "test" @:`True` $;
false = "test" @:`False` $;
"""
model = tatsu.compile(grammar=grammar)
self.assertEqual('Test', model.directives.get('grammar'))
self.assertEqual('Test', model.name)
# By default, parsing starts from the first rule in the grammar.
ast = model.parse('test')
self.assertEqual(ast, True)
# The start rule can be passed explicitly.
ast = model.parse('test', start='true')
self.assertEqual(ast, True)
# Backward compatibility argument name.
ast = model.parse('test', rule_name='true')
self.assertEqual(ast, True)
# The default rule can be overwritten.
ast = tatsu.parse(grammar, 'test', start='false')
self.assertEqual(ast, False)
# Backward compatibility argument name.
ast = tatsu.parse(grammar, 'test', rule_name='false')
self.assertEqual(ast, False)
def test_rule_capitalization(self):
grammar = """
start = ['test' {rulename}] ;
{rulename} = /[a-zA-Z0-9]+/ ;
"""
test_string = 'test 12'
lowercase_rule_names = ['nocaps', 'camelCase', 'tEST']
uppercase_rule_names = ['Capitalized', 'CamelCase', 'TEST']
ref_lowercase_result = tatsu.parse(
grammar.format(rulename='reflowercase'), test_string,
)
ref_uppercase_result = tatsu.parse(
grammar.format(rulename='Refuppercase'), test_string,
)
for rulename in lowercase_rule_names:
result = tatsu.parse(
grammar.format(rulename=rulename), test_string,
)
self.assertEqual(result, ref_lowercase_result)
for rulename in uppercase_rule_names:
result = tatsu.parse(
grammar.format(rulename=rulename), test_string,
)
self.assertEqual(result, ref_uppercase_result)
def test_startrule_issue62(self):
grammar = """
@@grammar::TEST
file_input = expr $ ;
expr = number '+' number ;
number = /[0-9]/ ;
"""
model = tatsu.compile(grammar=grammar)
model.parse('4 + 5')
def test_skip_whitespace(self):
grammar = """
statement = 'FOO' subject $ ;
subject = name:id ;
id = /[a-z]+/ ;
"""
model = tatsu.compile(grammar=grammar)
ast = model.parse('FOO' + ' ' * 3 + 'bar', parseinfo=True)
print(json.dumps(asjson(ast), indent=2))
subject = ast[1]
assert subject['name'] == 'bar'
parseinfo = subject['parseinfo']
assert parseinfo.pos == parseinfo.tokenizer.text.index('bar')
def suite():
return unittest.TestLoader().loadTestsFromTestCase(ParsingTests)
def main():
unittest.TextTestRunner(verbosity=2).run(suite())
if __name__ == '__main__':
main()
|