Я пытаюсь создать грамматику для анализа некоторых формул, подобных Excel, которые я разработал, где специальный символ в начале строки означает другой источник. Например, $
может обозначать строку, поэтому "$This is text
" будет рассматриваться как входная строка в программе, а &
может обозначать функцию, поэтому &foo()
можно рассматривать как вызов внутренней функции foo
.
Проблема, с которой я сталкиваюсь, заключается в том, как правильно построить грамматику. Например, это упрощенная версия MWE:
grammar = r'''start: instruction
?instruction: simple
| func
STARTSYMBOL: "!"|"#"|"$"|"&"|"~"
SINGLESTR: (LETTER+|DIGIT+|"_"|" ")*
simple: STARTSYMBOL [SINGLESTR] (WORDSEP SINGLESTR)*
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: STARTSYMBOL SINGLESTR "(" [simple|func] (ARGSEP simple|func)* ")"
%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
parser = lark.Lark(grammar, parser='earley')
Таким образом, с этой грамматикой такие вещи, как: $This is a string
, &foo()
, &foo(#arg1)
, &foo($arg1,,#arg2)
и &foo(!w1,w2,w3,,!w4,w5,w6)
, анализируются должным образом. Но если я хочу добавить больше гибкости моему терминалу simple
, мне нужно начать возиться с определением токена SINGLESTR
, что неудобно.
Что я пробовал
Часть, которую я не могу пройти, заключается в том, что если я хочу иметь строку, содержащую круглые скобки (которые являются литералами func
), то я не могу обработать их в моей текущей ситуации.
- Если я добавлю круглые скобки в
SINGLESTR
, то получуExpected STARTSYMBOL
, потому что он смешивается с определениемfunc
и считает, что должен быть передан аргумент функции, что имеет смысл. - Если я переопределю грамматику, зарезервировав символ амперсанда только для функций, и добавлю скобки в
SINGLESTR
, тогда я смогу разобрать строку со скобками, но каждая функция, которую я пытаюсь разобрать, дастExpected LPAR
.
Мое намерение состоит в том, чтобы все, что начинается с $
, анализировалось как токен SINGLESTR
, а затем я мог бы анализировать такие вещи, как &foo($first arg (has) parentheses,,$second arg)
.
Мое решение на данный момент заключается в том, что я использую в своих строках слова-экраны, такие как LEFTPAR и RIGHTPAR, и я написал вспомогательные функции, чтобы преобразовать их в круглые скобки при обработке дерева. Итак, $This is a LEFTPARtestRIGHTPAR
создает правильное дерево, и когда я его обрабатываю, оно преобразуется в This is a (test)
.
Чтобы сформулировать общий вопрос: могу ли я определить свою грамматику таким образом, чтобы некоторые символы, которые являются специальными для грамматики, рассматривались как обычные символы в некоторых ситуациях и как специальные символы в любом другом случае?
РЕДАКТИРОВАТЬ 1
Основываясь на комментарии от jbndlr
, я пересмотрел свою грамматику, чтобы создать отдельные режимы на основе начального символа:
grammar = r'''start: instruction
?instruction: simple
| func
SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|")")*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"
%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
Это подпадает (отчасти) под мой второй тестовый пример. Я могу анализировать все simple
типов строк (токены TEXT, MD или DB, которые могут содержать круглые скобки) и пустые функции; например, &foo()
или &foo(&bar())
анализируются правильно. В тот момент, когда я помещаю аргумент в функцию (независимо от типа), я получаю UnexpectedEOF Error: Expected ampersand, RPAR or ARGSEP
. В качестве доказательства концепции, если я уберу круглые скобки из определения SINGLESTR в новой грамматике выше, тогда все будет работать как надо, но я вернусь к исходной точке.
STARTSYMBOL
), и вы добавляете разделители и круглые скобки там, где это необходимо для ясности; Я не вижу здесь двусмысленности. Вам все равно придется разделить списокSTARTSYMBOL
на отдельные элементы, чтобы их можно было различить. - person jbndlr   schedule 27.11.2019