【问题标题】:Unary minus messes up parsing一元减法搞砸了解析
【发布时间】:2020-01-29 05:20:08
【问题描述】:

这里是要解析的语言id'的语法:

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))"

欢迎任何帮助。Full code here

【问题讨论】:

  • 您似乎正在使用 Haskell 分词器,它会在解析开始之前将 --2 作为注释删除。
  • 我认为不是:请参阅其他问题:" -1+-2*3" 提供"Uno Oppo (Con 1)"

标签: parsing haskell parsec


【解决方案1】:

reservedOp 的目的是创建一个解析器(您已将其命名为 m_reservedOp),它解析给定的运算符符号字符串,同时确保它不是更长的前缀运算符符号字符串。从源码中reservedOp的定义可以看出这一点:

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

请注意,提供的name 仅在它后面没有任何opLetter 符号时才会被解析。

在您的情况下,m_reservedOp "-" 无法解析字符串 "--2",因为即使它以有效运算符 "-" 开头,该字符串也作为更长有效运算符 "--" 的前缀出现。

在具有单字符运算符的语言中,您可能根本不想使用reservedOp,除非您想在不插入空格的情况下禁止相邻的运算符。只需使用symbol "-",它将始终解析"-",无论后面是什么(并消耗后面的空格,如果有的话)。此外,在具有固定集合 运算符(即没有用户定义的运算符)的语言中,您可能不会使用operator 解析器,因此您不需要opStart,或reservedOpNames。如果没有reservedOpoperator,则不会使用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 解析一个字符,后跟零个或多个identLetters,直到第一个非identLetter。 (它从不解析部分标识符,因此它永远不会在桌面上留下更多 identLetters。)此外,它会检查该标识符是否不在列表中 reservedNames
  • symbol - 解析给定的字符串。如果字符串是保留字,则不检查它是否不是更大的有效标识符的一部分。因此,symbol "for" 将匹配 foreground = "black" 的开头,这很少是您想要的。请注意,symbol 没有使用 identStartidentLetterreservedNames
  • reserved - 解析给定的字符串,然后确保它后面没有identLetter。因此,m_reserved "for" 将解析 for (i=1; ...,但 不会 解析 foreground = "black"。通常,提供的字符串将是一个有效的标识符,但不会对此进行检查,因此您可以根据需要编写 m_reserved "15" —— 使用具有通常类型的字母数字标识符的语言,这将解析 "15" 提供它后面没有字母或其他数字。此外,可能有点令人惊讶的是,没有检查提供的字符串是否在 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 开头,后跟零个或多个 opLetters 直到第一个非 opLetter。因此,operator 应用于字符串 "--2" 将始终采用整个运算符 "--",而不仅仅是前缀 "-"。额外检查生成的运算符不在reservedOpNames 列表中。
  • symbol - 与标识符完全相同。它解析一个没有检查或引用opStartopLetterreservedOpNames的字符串,所以symbol "-"将解析字符串"--"的第一个字符就好了,留下第二个"-"字符稍后解析器。
  • reservedOp - 解析给定的字符串,确保它后面没有 opLetter。因此,m_reservedOp "-" 将解析 "-x" 的开头但不解析 "--2",假设 - 匹配 opLetter。和以前一样,不检查字符串是否在 reservedOpNames 中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-02
    • 1970-01-01
    • 2013-01-10
    • 2010-10-15
    • 2012-04-14
    相关资源
    最近更新 更多