-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest.py
More file actions
146 lines (111 loc) · 4.04 KB
/
test.py
File metadata and controls
146 lines (111 loc) · 4.04 KB
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
import math
import re
from parser_edsl import *
# Словарь констант (понадобится в примере)
CONSTANTS = {
'pi': math.pi,
'e': math.e,
'Na': 6.02214076e23, # Постоянная Больцмана
'kB': 1.380649e-23, # Число Авогадро
}
# ZeroDiv и checked_div нужны для примера учёта координат в действиях
class ZeroDiv(Error):
def __init__(self, pos):
self.pos = pos
@property
def message(self):
return 'Деление на нуль (или ноль)'
def checked_div(values, coords, res_coord):
x, y = values
cx, cdiv, cy = coords
if y != 0:
return x / y
else:
raise ZeroDiv(cy)
# Определения нетерминальных символов
Expr = NonTerminal('Expr')
Term = NonTerminal('Term')
Factor = NonTerminal('Factor')
# Определения терминальных символов
# Строка из десятичных цифр подходит под оба регулярных выражения,
# поэтому для целых чисел повышаем приоритет (по умолчанию — 5)
integer = Terminal('INTEGER', '[0-9]+', int, priority=7)
real = Terminal('REAL', '[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?', float)
# Ключевое слово без учёта регистра (тоже повышаем приоритет)
kw_mod = Terminal('MOD', 'mod', lambda x: None,
re_flags=re.IGNORECASE, priority=10)
const = Terminal('CONST', '[A-Za-z]+', str)
# Определение правил грамматики
Expr |= Expr, '+', Term, lambda x, y: x + y
Expr |= Expr, '-', Term, lambda x, y: x - y
Expr |= Term
Term |= Term, '*', Factor, lambda x, y: x * y
Term |= Term, '/', Factor, ExAction(checked_div) # Деление с проверкой
Term |= Term, kw_mod, Factor, lambda x, y: x % y
Term |= Factor
Factor |= integer
Factor |= real
Factor |= const, lambda name: CONSTANTS[name]
Factor |= '(', Expr, ')'
# Создаём парсер и проверяем грамматику на LALR(1)
p = Parser(Expr)
assert p.is_lalr_one()
# Игнорируем пробельные символы и комментарии вида {...}
p.add_skipped_domain('\\s')
p.add_skipped_domain('\\{.*?\\}')
# Тесты
def test(text):
try:
print('ОК:', p.parse(text))
except Error as e:
print(f'Ошибка {e.pos}: {e.message}')
def title(message):
print()
print(message)
print('-' * len(message))
title('Примеры успешного разбора:')
test('1')
test('1 {комментарий} + 2')
test('2 + 3.5*4/(76-6)')
test('1e+2 + 1e-2')
test('100 mod 7')
test('100 Mod 7')
test('100 MOD 7')
test('pi + e')
test('kB * Na')
title('Примеры синтаксических и лексических ошибок:')
test('2 + 3.5*4/(76-6)+')
test('2 + 3.5*4/(76-6))')
test('2 + 3.5*4/(76-6)1')
test('2 + 3.5*4/(76-6')
test('''
2 + 3*4
*(5@6)
'''.strip())
test('')
test('2 + 3.5*4/(76-76)')
title('Токены:')
for token in p.tokenize('3e8+{комментарий}*\n( /-100500mod100.500)'):
print(token.pos, ':', token)
# Пример отладки не-LALR(1)-грамматики — у неё is_lalr_one() вернёт False,
# в отладочной печати таблицы можно наблюдать конфликты
title('Пример на не-LALR(1)-грамматику:')
pal = NonTerminal('palindrome')
pal |= 'a'
pal |= 'a', pal, 'a'
pal_par = Parser(pal)
print('Грамматика палиндромов LALR(1)?', pal_par.is_lalr_one())
print()
print('Таблица грамматики палиндромов:')
pal_par.print_table()
# Пример на работу с пустыми правилами
title('Работа с пустой грамматикой:')
Empty = NonTerminal('Empty')
Empty |= ()
parser_empty = Parser(Empty)
assert parser_empty.is_lalr_one()
print(parser_empty.parse(''))
try:
parser_empty.parse('x')
except Error as e:
print(e.pos, e.message)