Это похоже на рекурсивную структуру (поскольку, скорее всего, операнды также могут быть выражениями).В этом случае хорошей идеей будет написание парсера .Самостоятельное написание парсера обычно, однако, подвержено ошибкам и обременительно.Таким образом, мы не собираемся писать синтаксический анализатор самостоятельно, мы используем инструмент, который можем дать спецификации, а затем генерировать сам синтаксический анализатор.
Одним из этих инструментов является, например, PLY .Простой анализатор (я не буду реализовывать полный анализатор, но идея должна быть ясна) может выглядеть следующим образом.
Лексер
Сначала нам нужно реализоватьлексер, который анализирует токены :
# lexer.py
import ply.lex as lex
tokens = (
'AND',
'OR',
'IDENTIFIER',
)
t_AND = r'AND'
t_OR = r'OR'
def t_IDENTIFIER(t):
r'[a-z_]+'
return t
t_ignore = ' \t\r\n'
lexer = lex.lex()
Приведенное выше приведёт к лексеру (также известному как tokenizer ; не анализатору).Лексер превращает строку в список токенов.Здесь есть три возможных токена: AND
, OR
и IDENTIFIER
.AND
соответствует только 'AND'
(в верхнем регистре), OR
соответствует 'OR' (в верхнем регистре), а IDENTIFIER
соответствует всему, что представляет собой последовательность символов нижнего регистра и подчеркивания.
Так что если мыпроанализировав строку, мы получим:
>>> from lexer import lexer
>>> lexer.input('foo AND bar')
>>> lexer.token()
LexToken(IDENTIFIER,'foo',1,0)
>>> lexer.token()
LexToken(AND,'AND',1,4)
>>> lexer.token()
LexToken(IDENTIFIER,'bar',1,8)
>>> lexer.token()
>>>
Парсер
Теперь мы можем превратить список токенов в «дерево», содержащее листья (идентификаторы) и inodes (операнды):
# parser.py
import ply.yacc as yacc
class Identifier:
def __init__(self, name):
self.name = name
def resolve(self, dictionary):
return dictionary[self.name]
class Node:
def __init__(self, left, right):
self.left = left
self.right = right
def resolve(self, dictionary):
return self.func(self.left.resolve(dictionary), self.right.resolve(dictionary))
def func(self, left, right):
return None
class AndNode(Node):
def func(self, left, right):
return left and right
class OrNode(Node):
def func(self, left, right):
return left or right
from lexer import tokens
def p_expression_or(p):
'expression : and_exp OR expression'
p[0] = OrNode(p[1], p[3])
def p_expression_or_no(p):
'expression : and_exp'
p[0] = p[1]
def p_expression_and(p):
'and_exp : ident AND and_exp'
p[0] = AndNode(p[1], p[3])
def p_expression_and_no(p):
'and_exp : ident'
p[0] = p[1]
def p_ident(p):
'ident : IDENTIFIER'
p[0] = Identifier(p[1])
parser = yacc.yacc()
Здесь мы указываем набор правил производства вместе с логикой для обработки этого правила производства.Мы указываем, что expression
- это and_expr
, за которым следует OR
, за которым следует еще expression
(первая функция) или просто and_expr
(вторая функция).Таким образом, мы строим грамматику языка.В функциях мы создаем объекты AndNode
, OrNode
и Identifier
в виде дерева.
Оцениваем синтаксическое дерево
Теперь мыможно разобрать строку в такое дерево с помощью:
from parser import parser
tree = parser.parse('foo AND bar')
Теперь со словарем вроде:
data = {'foo': True, 'bar': True}
мы можем вызвать .resolve(..)
метод tree
и получитьрезультат:
>>> tree.resolve({'foo': True, 'bar': True})
True
>>> tree.resolve({'foo': True, 'bar': False})
False
>>> tree.resolve({'foo': False, 'bar': False})
False
Расширение синтаксического анализатора
Если вы прочитаете документацию, вы найдете способы включить круглые скобки и другие функции (унарные операторы, бинарные операторы, функции) и т. д. длялексер, анализатор и оцените их.