Megaparsec:无法解析递归算术字符串

时间:2017-04-08 12:56:37

标签: haskell megaparsec

我正在使用Megaparsec进行小型解析器并尝试解析算术。

-- Arithmetic expressions
data Aexp = N Num 
            | V Var 
            | Mult Aexp Aexp
            | Add Aexp Aexp 
            | Sub Aexp Aexp 
             deriving (Show, Eq, Read)


arithParser :: Parser Aexp
arithParser = V <$> strParser
            <|> N <$> numParser
            <|> Mult <$> arithParser <* tok "*" <*> arithParser
--boolParser :: Parser Bexp


strParser :: Parser Var
strParser = tok "\"" *> some (noneOf ("\n\r\"=[]{},:")) <* tok "\""

numParser :: Parser Num
numParser = (some (oneOf ['0' .. '9']) >>= return . read) <* whitespace

如果我运行命令Parse arithParser "5*5" "5*5",它只返回Right (N 5),它应返回Mult(N 5) (N 5)。因为arithParser的优先级。但是,如果我改变顺序,那么它似乎会进入无限循环而崩溃。

不确定我在这里做错了什么,我们将不胜感激。

2 个答案:

答案 0 :(得分:10)

Parsec在尝试正确的<|>之前尝试左侧替代5*5。如果左侧替代方案成功,那么它就不会为正确的方式而烦恼。所以在这种情况下,当输入输入V <$> strParser时,Parsec的过程如下所示:

  1. 试试strParsertok "\""'"'开头,但输入字符串不以strParser开头,因此N <$> numParser失败。
  2. 试试numParserN 5成功解析了数字5,因此Parsec只返回Mult
  3. 完成!无需尝试第三种选择。
  4. 因此,我们可以尝试通过将try选项移到顶部来修补此解析器,包含在numParser中,以便它可以回溯并尝试strParser或{{1如果输入结果不是乘法。

    arithParser :: Parser Aexp
    arithParser = try (Mult <$> arithParser <* tok "*" <*> arithParser)
                <|> N <$> numParser
                <|> V <$> strParser
    

    这个解析器有另一个更微妙的问题。让我们按照上面的步骤进行操作。

    1. 试试try (Mult <$> arithParser <* tok "*" <*> arithParser)。此解析器以arithParser开头,因此递归调用arithParser
    2. 试试try (Mult <$> arithParser <* tok "*" <*> arithParser)。此解析器以arithParser开头,因此递归调用arithParser
    3. 试试try (Mult <$> arithParser <* tok "*" <*> arithParser)。此解析器以arithParser开头,因此递归调用arithParser
    4. ...
    5. 这是一个无限循环。 Parsec无法处理左递归语法。您必须设计解析器,以便在递归调用之前至少使用一个令牌。这样做的一种常见方式是“平坦化”#34;你的语法:

      expr, term :: Parser AExp
      expr = do
          n <- term
          rest <- optional $ tok "*" *> expr
          return $ maybe n (Mult n) rest
      term = N <$> numParser
          <|> V <$> strParser
          <|> parenthesised expr
      
      parenthesised = between (char '(') (char ')')
      

      在这里,我将解析器拆分成一个解析任意expr - 一个term,后面跟一个乘法符号和一个被乘数expr - 并解析一个单term s - 数字,字符串和带括号的表达式。对expr的递归调用现在可以正常 - 只有在您解析了expr(总是消耗输入)和term内的term之后才会发生expr内的调用。只有在您解析了一个左括号后才会发生。

      请注意expr = makeExprParser term [[InfixR $ tok "*" $> Mult]] 具有类似列表的结构:它解析一个可能后跟很多东西的东西。一般来说,您应该考虑解析器使用输入令牌的线性输入流,因此列表形解析器往往比树形解析器更有效并不奇怪。

      Text.Megaparsec.Expr模块包含打包此模式的函数,并使用任意优先级和固定规则解析表达式。

      df['islower'] = df['islower'].where(df['Data'] < df['lower'], 1, 0)    
      df['isupper'] = df['isupper'].where(df['Data'] < df['upper'], 1, 0)    
      

答案 1 :(得分:0)

这些类型对您说谎:当您定义递归解析器p时,您实际上并不允许在任何您想要的地方使用p!您需要先输入部分输入,以确保您取得进步。否则Haskell确实会愉快地进入无限循环。

这个问题通常可以通过定义不同的&#34;&#34;来解决。表达式,只允许&#34;更简单&#34;一个或括号包裹&#34;更复杂&#34;处于左递归位置的那些(因为匹配一个打开的括号会强制你通过输入字符串的一部分)。

E.g。你的表达式的语法将变成(从最简单到最复杂):

<Literal> ::= [0-9]+
<Var>     ::= [a-zA-Z]+
<Base>    ::= '(' <Expr> ')' | <Var> | <Literal>
<Factor>  ::= <Base> | <Base> '*' <Factor>
<Expr>    ::= <Factor> | <Factor> '+' <Expr> | <Factor> '-' <Expr>

这是一个总体语言闪耀的地方:因为在终止时类型必须完全诚实,所以编写这些表现不佳的左递归解析器几乎是不可能的。类型检查器告诉您,您必须找到另一种识别语言术语的方法。

例如我在我的整个解析器组合器库中使用的fixpoint combinator fix没有类型(a -> a) -> a,而是(忽略有趣的括号)(□ a → a) → a这会精确地阻止你在你取得一些进展之前使用递归调用。你仍然可以写一个parser for Expr,但是当你进行非法移动时,类型检查员会在这里发出警告。