Parsec:回溯不起作用

时间:2010-03-08 18:57:37

标签: haskell f# parsec backtracking

我正在尝试解析F#类型语法。我开始编写[F] Parsec语法并遇到问题,所以我将the grammar简化为:

type ::= identifier | type -> type
identifier ::= [A-Za-z0-9.`]+

在遇到FParsec问题后,我切换到Parsec,因为我有一个full chapter of a book dedicated to explaining it。我的语法代码是

typeP = choice [identP, arrowP]
identP = do
   id <- many1 (digit <|> letter <|> char '.' <|> char '`')
   -- more complicated code here later
   return id
arrowP = do
  domain <- typeP
  string "->"
  range <- typeP
  return $ "("++domain++" -> "++range++")"
run = parse (do t <- typeP
                eof
                return t) "F# type syntax"

问题是Parsec默认没有回溯,所以

> run "int"
Right "int"
-- works! 
> run "int->int"
Left "F# type syntax"
unexpected "-"
expecting digit, letter, ".", "`" or end of input
-- doesn't work!

我尝试的第一件事是重新排序typeP:

typeP = choice [arrowP, identP]

但这只是堆栈溢出,因为语法是左递归的 - typeP永远不会尝试identP,因为它一直在尝试arrowP。接下来,我在各个地方尝试try,例如:

typeP = choice [try identP, arrowP]

但我所做的一切似乎都没有改变(1)堆栈溢出或(2)不识别“ - &gt;”的基本行为。遵循标识符。

对于任何成功编写过Parsec语法的人来说,我的错误可能是显而易见的。有人能说出来吗?

3 个答案:

答案 0 :(得分:5)

我认为问题在于,我正在为F#做一个假设(因为我不知道),箭头是正确的关联。我不确定链接语法应该有多精确,因为我不熟悉不同的语法。但是,如果我们可以假设箭头是正确的关联,使问题更容易。

因此,假设我们可以做到这一点:

identP = many1 (digit <|> letter <|> char '.' <|> char '`')

typeP = try arrowP <|> identP

arrowP = do
  i <- identP
  string "->"
  t <- typeP
  return $ "(" ++ i ++ " -> " ++ t ++ ")"

run = flip parse "F# type syntax" $ do
        t <- typeP
        eof
        return t

所以:

Haskell> run "int"
Right "int"
Haskell> run "int->int"
Right "(int -> int)"
Haskell> run "int->int->int->int"
Right "(int -> (int -> (int -> int)))"

进一步扩展,可能令你困惑的是,在那个语法中它表示类型 - &gt;类型,这意味着你可以在左侧有一个箭头。那没关系,但需要在括号中。这有帮助,也许看到以下实际行动是有帮助的。它帮助了我。

typeP = try arrowP <|> parens typeP <|> identP

arrowP = do
 i <- parens typeP <|> identP
 string "->"
 t <- typeP
 return $ "(" ++ i ++ " -> " ++ t ++ ")"

parens p  = between (char '(') (char ')') p

现在我们可以在箭头的左侧或右侧写下箭头:

Haskell> run "int->int->int"
Right "(int -> (int -> int))"
Haskell> run "(int->int)->int"
Right "((int -> int) -> int)"

答案 1 :(得分:4)

我认为你应该将左递归从语法中分解出来。而不是

type ::= identifier | type -> type 
identifier ::= [A-Za-z0-9.`]+ 

你会得到像

这样的东西
typeStart ::= identifier 
type ::= typeStart (-> type)?
identifier ::= [A-Za-z0-9.`]+ 

然后,我认为这将更容易直接转换为parsec。 (人们会认为try会起作用,我希望它能以某种方式发挥作用,但是,我的经验也是我必须至少在Parsec腰部以至于我才明白“放在哪里{{1 “使事情有效。”

考虑一些基础知识,还要考虑Monadic Parser Combinators in F#(以及之前的7个C#博客条目)。我认为parsec docs(尝试只读它们自上而下,它们是正确的,如果我没记错的话)以及各种研究论文中的一些例子谈论问题中的问题。

答案 2 :(得分:0)

这不会帮助您了解出错的地方,但我建议您使用sepBy1来解析由->符号分隔的类型。这将为您提供已解析类型的列表,然后您可以将其转换回函数类型。