Одна из слабостей программы parser1 состоит в том, что она встраивает логику вычисления выражений в логику синтаксического анализа: результат вычисляется во время грамматического разбора строки. Это ускоряет вычисление, но может затруднить модификацию программного кода, особенно при росте системы. Проще говоря, можно изменить организацию программы с тем, чтобы отделить синтаксический анализ от вычисления. Вместо вычисления строки парсер может построить промежуточное представление, которое будет вычислено позднее. Дополнительным стимулом к созданию такого отдельного представления является возможность подвергнуть его анализу другими средствами (например, оптимизаторами, инструментами просмотра и так далее) — они могут запускаться в отдельных проходах по дереву.
Пример 19.16. PP4E\Lang\Parser\parser2.py
Синтаксический анализ, выполняемый отдельно от вычисления, конструирующий дерево синтаксического анализа
TraceDefault = False
class UndefinedError(Exception): pass
if __name__ == ‘__main__’:
from scanner import Scanner, SyntaxError, LexicalError # запускается здесь else:
from .scanner import Scanner, SyntaxError, LexicalError # из PyTree
############################################################################ # интерпретатор (дерево умных объектов)
############################################################################
class TreeNode:
def validate(self, dict): # проверка ошибок по умолчанию
pass
def apply(self, dict): # механизм вычисления по умолчанию
pass
def trace(self, level): # механизм анализа дерева по умолчанию
print(‘.’ * level + ‘<empty>’)
# КЛАССЫ КОРНЕВЫХ ОБЪЕКТОВ
class BinaryNode(TreeNode):
def __init__(self, left, right): # наследуемые методы
self.left, self.right = left, right # левая/правая ветви
def validate(self, dict):
self.left.validate(dict) # ветви рекурсивного спуска
self.right.validate(dict)
def trace(self, level):
print(‘.’ * level + ‘[‘ + self.label + ‘]’)
self.left.trace(level+3)
self.right.trace(level+3)
class TimesNode(BinaryNode):
label = ‘*’
def apply(self, dict):
return self.left.apply(dict) * self.right.apply(dict)
class DivideNode(BinaryNode):
label = ‘/’
def apply(self, dict):
return self.left.apply(dict) / self.right.apply(dict)
class PlusNode(BinaryNode):
label = ‘+’
def apply(self, dict):
return self.left.apply(dict) + self.right.apply(dict)
class MinusNode(BinaryNode): label = ‘-‘
def apply(self, dict):
return self.left.apply(dict) — self.right.apply(dict)
# КЛАССЫ ОБЪЕКТОВ-ЛИСТЬЕВ
class NumNode(TreeNode):
def __init__(self, num):
self.num = num # уже число
def apply(self, dict): # проверка по умолчанию
return self.num
def trace(self, level):
print(‘.’ * level + repr(self.num)) # как прогр. код, было ‘self.num’
class VarNode(TreeNode):
def __init__(self, text, start):
self.name = text # имя переменной
self.column = start # номер позиции для ошибок
def validate(self, dict):
if not self.name in dict.keys():
raise UndefinedError(self.name, self.column)
def apply(self, dict):
return dict[self.name] # сначала проверить
def assign(self, value, dict):
dict[self.name] = value # локальное расширение
def trace(self, level):
print(‘.’ * level + self.name)
# КЛАССЫ КОМПОЗИТНЫХ ОБЪЕКТОВ
class AssignNode(TreeNode):
def __init__(self, var, val): self.var, self.val = var, val
def validate(self, dict):
self.val.validate(dict) # не проверять переменные
def apply(self, dict):
self.var.assign( self.val.apply(dict), dict )
def trace(self, level):
print(‘.’ * level + ‘set ‘)
self.var.trace(level + 3)
self.val.trace(level + 3)
############################################################################ # парсер (синтаксический анализатор, построитель дерева)
############################################################################
class Parser:
def __init__(self, text=»):
self.lex = Scanner(text) # создать сканер
self.vars = {‘pi’:3.14159} # добавить константы
self.traceme = TraceDefault