Унарный минус портит парсинг - PullRequest
1 голос
/ 28 января 2020

Вот грамматика идентификатора языка 'как для анализа:

expr ::= val | const | (expr) | unop expr | expr binop expr
var ::= letter
const ::= {digit}+
unop ::= -
binop ::= /*+-

Я использую пример из haskell wiki .

Семантика и синтаксический анализатор токенов здесь не показаны.

exprparser = buildExpressionParser table term <?> "expression"

table = [ [Prefix (m_reservedOp "-" >> return (Uno Oppo))] 
         ,[Infix (m_reservedOp "/"  >> return (Bino Quot)) AssocLeft
          ,Infix (m_reservedOp "*"  >> return (Bino Prod)) AssocLeft]
         ,[Infix (m_reservedOp "-"  >> return (Bino Diff)) AssocLeft
          ,Infix (m_reservedOp "+"  >> return (Bino Somm)) AssocLeft]
        ]

term = m_parens exprparser
       <|> fmap Var m_identifier
       <|> fmap Con m_natural

Символ минус появляется два раза, один раз как одинарный, один раз как двоичный оператор.

На входе "1--2" синтаксический анализатор дает только Con 1

вместо ожидаемого "Bino Diff (Con 1) (Uno Oppo (Con 2))"

Любая помощь приветствуется. Полный код здесь

1 Ответ

2 голосов
/ 29 января 2020

Цель reservedOp - создать парсер (который вы назвали m_reservedOp), который анализирует заданную строку символов оператора, при этом гарантируя, что это , а не префикс более длинной строки операторских символов. Это можно увидеть из определения reservedOp в источнике:

reservedOp name =
    lexeme $ try $
    do{ _ <- string name
      ; notFollowedBy (opLetter languageDef) <?> ("end of " ++ show name)
      }

Обратите внимание, что поставляемый name анализируется, только если за ним не следуют никакие символы opLetter.

В вашем случае строка "--2" не может быть проанализирована с помощью m_reservedOp "-", потому что, даже если она начинается с действительного оператора "-", эта строка встречается как префикс более длинного действительного оператора "--".

В языке с односимвольными операторами вы, вероятно, вообще не хотите использовать reservedOp, если только вы не хотите запретить смежные операторы без пробелов. Просто используйте symbol "-", который всегда будет анализировать "-", независимо от того, что следует (и использовать следующие пробелы, если они есть). Кроме того, на языке с фиксированным набором операторов (т. Е. Без пользовательских операторов) вы, вероятно, не будете использовать синтаксический анализатор operator, поэтому вам не понадобится opStart или reservedOpNames. Без reservedOp или operator синтаксический анализатор opLetter не используется, так что вы также можете его удалить.

Это, вероятно, довольно запутанно, а документация Parsec объясняет ужасную работу как должен работать «зарезервированный» механизм. Вот пример:

Давайте начнем с идентификаторов, а не операторов. В типичном языке, который допускает определяемые пользователем идентификаторы (т. Е. Практически любой язык, поскольку «переменные» и «функции» имеют определяемые пользователем имена), а также может иметь некоторые зарезервированные слова, которые не допускаются в качестве идентификаторов, соответствующие настройки в GenLanguageDef входят:

identStart       -- parser for first character of valid identifier
identLetter      -- second and following characters of valid identifier
reservedNames    -- list of reserved names not allowed as identifiers

Лексемы (поглощающие пробелы), созданные с использованием объекта GenTokenParser:

  • identifier - анализирует неизвестное, определяемый пользователем идентификатор. Он анализирует символ от identStart с последующим нулем или более identLetter с до первого не identLetter. (Он никогда не анализирует частичный идентификатор, поэтому он никогда не оставит больше identLetter s в таблице.) Кроме того, он проверяет, что идентификатора нет в списке reservedNames.
  • symbol - Разбирает заданную строку. Если строка является зарезервированным словом, не выполняется проверка того, что она не является частью большего допустимого идентификатора. Таким образом, symbol "for" будет соответствовать началу foreground = "black", что редко является тем, что вы хотите. Обратите внимание, что symbol не использует identStart, identLetter или reservedNames.
  • reserved - анализирует данную строку, а затем гарантирует, что за ней не следует identLetter. Итак, m_reserved "for" будет анализировать for (i=1; ..., но не анализировать foreground = "black". Обычно предоставленная строка будет допустимым идентификатором, но проверка не выполняется, поэтому вы можете написать m_reserved "15", если хотите - на языке с обычными типами идентификаторов alphanumeri c, это будет анализировать "15" при условии, что за ним не следует ни письмо, ни другое git. Также, что может быть несколько удивительно, не выполняется проверка того, что поставляемая строка находится в reservedNames.

Если это имеет смысл для вас, тогда настройки оператора следуют точно такой же схеме. Соответствующие настройки:

opStart          -- parser for first character of valid operator
opLetter         -- valid second and following operator chars, for multichar operators
reservedOpNames  -- list of reserved operator names not allowed as user-defined operators

и соответствующие анализаторы:

  • operator - анализирует неизвестный пользовательский оператор, начинающийся с opStart и сопровождаемый ноль или более opLetter с до первого не opLetter. Таким образом, operator, примененный к строке "--2", всегда будет принимать весь оператор "--", а не только префикс "-". Дополнительная проверка выполняется, что результирующий оператор не в списке reservedOpNames.
  • symbol - точно так же, как для идентификаторов. Он анализирует строку без проверок или ссылок на opStart, opLetter или reservedOpNames, поэтому symbol "-" будет анализировать первый символ строки "--" очень хорошо, оставляя второй символ "-" для позже парсер.
  • reservedOp - анализирует данную строку, следя за тем, чтобы за ней не следовал opLetter. Таким образом, m_reservedOp "-" будет анализировать начало "-x", но не "--2", предполагая, что - соответствует opLetter. Как и прежде, не выполняется проверка того, что строка находится в reservedOpNames.
...