Этот тип синтаксиса действительно является целью pyparsing. Базовый формат достаточно прост, а в pyparsing он выглядит так:
decision = '(' + '('+question+')' + '('+action+')' + '('+action+')' + ')'
Но как только вы начинаете добавлять арифметические выражения, а также логические операции и поддержку операторов 'и' и 'или', все становится сложнее. Кроме того, это рекурсивная грамматика, поскольку само действие может быть вложенным решением.
Pyparsing имеет встроенную поддержку, которая упрощает определение арифметических и логических выражений, включая приоритет операций и группировку в скобках, а также рекурсивные выражения. Вот грамматика pyparsing в различных частях. Сначала приведем некоторые основные элементы синтаксического анализа:
LPAR,RPAR = map(Suppress,"()")
number = Regex(r"[+-]?\d+(\.\d*)?").setParseAction(lambda tokens:float(tokens[0]))
varname = Word(alphas+'_', alphanums+'_')
Действие разбора, приложенное к числовому выражению, автоматически преобразует проанализированное число в значение с плавающей точкой во время разбора. Класс Word принимает две строки: строку, содержащую все допустимые начальные символы, и строку всех допустимых символов тела. Это определение varname поддерживает имена переменных, похожие на идентификаторы Python.
Pyparsing имеет метод operatorPrecedence
, который принимает выражение для определения базового операнда, и список кортежей для определения каждого уровня операторов: выражение для операторов, целое число, является ли оператор унарным, двоичным или троичным и будь то левый или правый ассоциативный. operatorPrecedence заботится о рекурсивном определении арифметических выражений, вложенных в скобки. Это выражение определяет основную математику с 4 функциями:
operand = number | varname
arithExpr = operatorPrecedence(operand,
[
(oneOf("+ -"), 1, opAssoc.RIGHT),
(oneOf("* /"), 2, opAssoc.LEFT),
(oneOf("+ -"), 2, opAssoc.LEFT),
])
Теперь вот определение логического условия (которое мы в конечном итоге будем использовать для определения вопроса о решении):
TRUE = CaselessKeyword("TRUE") | Keyword("T")
FALSE = CaselessKeyword("FALSE") | Keyword("F")
comparisonOp = oneOf("< > <= >= = !=")
boolLiteral = TRUE | FALSE
arithComparison = arithExpr + comparisonOp + arithExpr
boolOperand = boolLiteral | arithComparison
booleanExpr = operatorPrecedence(boolOperand,
[
('not', 1, opAssoc.RIGHT),
('and', 2, opAssoc.LEFT),
('or', 2, opAssoc.LEFT),
])
Ваше определение действий было немного схематичным, поэтому я составил несколько возможных утверждений: оператор присваивания, оператор print
, и, поскольку это речевое приложение, оператор say
, который очень похож на print
.
rhs = booleanExpr | arithExpr
assignment = varname("assign_var") + '=' + Group(rhs)("assign_value")
print_cmd = Keyword("print") + Group(delimitedList(rhs | quotedString))
say_cmd = Keyword("say") + Group(delimitedList(rhs | quotedString))
cmd = print_cmd | say_cmd | assignment
В дополнение к определениям выражений вы заметите, что за некоторыми выражениями следует строка в кавычках, как будто выражение является функцией, вызываемой с этой строкой. Фактически, этот «вызов» фактически возвращает копию выражения, и соответствующие токены помечаются этим именем. Имена этих результатов очень полезны после разбора при выделении отдельных подходящих элементов (аналогично именованным группам в регулярных выражениях).
Наконец, чтобы объединить эти части в ваше выражение решения, вот выражения вопроса и действия:
IF = CaselessKeyword("IF")
question = LPAR + IF + Group(booleanExpr)("condition") + RPAR
action = LPAR + cmd + RPAR | Group(decision)
Обратите внимание, что определение действия может включать решение, но действие используется для определения решения. Чтобы разорвать эту зависимость от курицы и яйца, в этом разделе мы предваряем определение decision
, но с выражением-заполнителем с использованием класса pyparsing Forward
:
decision = Forward()
Затем после определения question
и action
мы используем оператор «<<» для «вставки» фактического определения выражения в существующую переменную <code>decision:
decision << ( LPAR +
question +
Group(action)("ifAction") +
Optional(Group(action)("elseAction")) +
RPAR)
Опять же, я взял на себя смелость с вашим определенным форматом, думая, что необязательный параметр else может быть полезен. Если вы не хотите, чтобы это было необязательным, просто удалите оболочку Optional
вокруг Group(action)("elseAction")
.
Это определяет грамматику, теперь вот несколько тестов. Использование функции dump () для результатов, возвращаемых parseString, является хорошим способом распечатать токены и любые связанные с ними имена.
tests = """\
((if TRUE)(a=99))
((if TRUE)(a = (a=99)))
((if a<100)(a = a + 1))
((if a<100)(a = a + 1)(a = a-1))
(
(if a<100)
(print a, "is too small")
( (if a>100) (print a,"is too big") (print a, "is just right") )
)
(
(if a<100)
(say a, "is too small!")
( (if a>100) (say a,"is too big!") (say a, "is just right!") )
)
"""
for d in decision.searchString(tests):
print d.dump()
print
Вот вывод:
['IF', ['TRUE'], ['a', '=', [99.0]]]
- condition: ['TRUE']
- ifAction: ['a', '=', [99.0]]
- assign_value: [99.0]
- assign_var: a
['IF', ['TRUE'], ['a', '=', ['a', '=', 99.0]]]
- condition: ['TRUE']
- ifAction: ['a', '=', ['a', '=', 99.0]]
- assign_value: ['a', '=', 99.0]
- assign_var: a
['IF', ['a', '<', 100.0], ['a', '=', [['a', '+', 1.0]]]]
- condition: ['a', '<', 100.0]
- ifAction: ['a', '=', [['a', '+', 1.0]]]
- assign_value: [['a', '+', 1.0]]
- assign_var: a
['IF', ['a', '<', 100.0], ['a', '=', [['a', '+', 1.0]]],
['a', '=', [['a', '-', 1.0]]]]
- condition: ['a', '<', 100.0]
- elseAction: ['a', '=', [['a', '-', 1.0]]]
- assign_value: [['a', '-', 1.0]]
- assign_var: a
- ifAction: ['a', '=', [['a', '+', 1.0]]]
- assign_value: [['a', '+', 1.0]]
- assign_var: a
['IF', ['a', '<', 100.0], ['print', ['a', '"is too small"']],
[['IF', ['a', '>', 100.0], ['print', ['a', '"is too big"']],
['print', ['a', '"is just right"']]]]]
- condition: ['a', '<', 100.0]
- elseAction: [['IF', ['a', '>', 100.0], ['print', ['a', '"is too big"']],
['print', ['a', '"is just right"']]]]
- ifAction: ['print', ['a', '"is too small"']]
['IF', ['a', '<', 100.0], ['say', ['a', '"is too small!"']],
[['IF', ['a', '>', 100.0], ['say', ['a', '"is too big!"']],
['say', ['a', '"is just right!"']]]]]
- condition: ['a', '<', 100.0]
- elseAction: [['IF', ['a', '>', 100.0], ['say', ['a', '"is too big!"']],
['say', ['a', '"is just right!"']]]]
- ifAction: ['say', ['a', '"is too small!"']]
Вот ссылка на полную программу разбора - http://pastebin.com/DnaNrx7j.
Это только первый этап, анализирующий ввод. Следующим шагом является оценка выражения путем обработки возвращенных токенов. Пример pyparsing wiki SimpleBool.py (http://pyparsing.wikispaces.com/file/view/simpleBool.py) включает в себя пример присоединения действий разбора для преобразования проанализированных токенов в экземпляры вызываемого класса, которые упрощают оценку результатов.
Надеюсь, это поможет.