我正在使用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的优先级。但是,如果我改变顺序,那么它似乎会进入无限循环而崩溃。
不确定我在这里做错了什么,我们将不胜感激。
答案 0 :(得分:10)
Parsec在尝试正确的<|>
之前尝试左侧替代5*5
。如果左侧替代方案成功,那么它就不会为正确的方式而烦恼。所以在这种情况下,当输入输入V <$> strParser
时,Parsec的过程如下所示:
strParser
。 tok "\""
以'"'
开头,但输入字符串不以strParser
开头,因此N <$> numParser
失败。numParser
。 N 5
成功解析了数字5,因此Parsec只返回Mult
。因此,我们可以尝试通过将try
选项移到顶部来修补此解析器,包含在numParser
中,以便它可以回溯并尝试strParser
或{{1如果输入结果不是乘法。
arithParser :: Parser Aexp
arithParser = try (Mult <$> arithParser <* tok "*" <*> arithParser)
<|> N <$> numParser
<|> V <$> strParser
这个解析器有另一个更微妙的问题。让我们按照上面的步骤进行操作。
try (Mult <$> arithParser <* tok "*" <*> arithParser)
。此解析器以arithParser
开头,因此递归调用arithParser
。try (Mult <$> arithParser <* tok "*" <*> arithParser)
。此解析器以arithParser
开头,因此递归调用arithParser
。try (Mult <$> arithParser <* tok "*" <*> arithParser)
。此解析器以arithParser
开头,因此递归调用arithParser
。这是一个无限循环。 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,但是当你进行非法移动时,类型检查员会在这里发出警告。