Я пытаюсь создать грамматику для анализа некоторых формул, подобных 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
.
Мое намерение состоит в том, что все, что начинается с a $
, будет проанализировано как SINGLESTR
токен, и тогда я смогу разобрать такие вещи, как &foo($first arg (has) parentheses,,$second arg)
.
Мое решение, на данный момент, заключается в том, что я использую в своих строках слова escape, такие как 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
список на отдельные элементы, чтобы их можно было различить.Ответы:
Вывод:
Я надеюсь, что это то, что вы искали.
Это были сумасшедшие несколько дней. Я пытался жаворонок и потерпел неудачу. Я тоже попробовал
persimonious
иpyparsing
. Все эти разные парсеры имели одну и ту же проблему с токеном 'аргумент', потребляющим правильную скобку, которая была частью функции, в конечном итоге терпела неудачу, потому что скобки функции не были закрыты.Хитрость заключалась в том, чтобы выяснить, как определить правильную круглую скобку, которая «не особенная». Смотрите регулярное выражение для
MIDTEXTRPAR
в коде выше. Я определил его как правую скобку, за которой не следует разделение аргумента или конец строки. Я сделал это с помощью расширения регулярного выражения,(?!...)
которое соответствует только в том случае, если за ним не следуют,...
но не используются символы. К счастью, он даже позволяет сопоставить конец строки внутри этого специального расширения регулярного выражения.РЕДАКТИРОВАТЬ:
Вышеупомянутый метод работает, только если у вас нет аргумента, заканчивающегося на a), потому что тогда регулярное выражение MIDTEXTRPAR не поймает этого) и будет думать, что это конец функции, даже если есть еще аргументы для обработки. Кроме того, могут быть неоднозначности, такие как ... asdf) ,, ..., это может быть конец объявления функции внутри аргумента или «текстоподобный») внутри аргумента, и объявление функции продолжается.
Эта проблема связана с тем, что то, что вы описываете в своем вопросе, не является контекстно-свободной грамматикой ( https://en.wikipedia.org/wiki/Context-free_grammar ), для которой существуют такие парсеры, как lark. Вместо этого это контекстно-зависимая грамматика ( https://en.wikipedia.org/wiki/Context-sensitive_grammar ).
Причина в том, что это контекстно-зависимая грамматика, состоит в том, что вам нужно, чтобы анализатор «запомнил», что он вложен в функцию, и сколько уровней вложенности существует, и чтобы эта память была в некотором роде доступной внутри синтаксиса грамматики.
EDIT2:
Также взгляните на следующий синтаксический анализатор, который является контекстно-зависимым и, кажется, решает проблему, но имеет экспоненциальную временную сложность в количестве вложенных функций, поскольку он пытается проанализировать все возможные функциональные барьеры, пока не найдет тот, который работает. Я считаю, что она должна иметь экспоненциальную сложность, поскольку она не является контекстно-свободной.
источник
&
например.Проблема в том, что аргументы функции заключены в круглые скобки, где один из аргументов может содержать круглые скобки.
Одним из возможных решений является использование backspace \ before (или), когда оно является частью String
Аналогичное решение, используемое C, для включения двойных кавычек (") как части строковой константы, где строковая константа заключена в двойные кавычки.
Выход
источник