3

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

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

在遇到 FParsec 问题后,我转而使用 Parsec,因为我有一整章专门解释它。我的这个语法的代码是

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)在标识符后面不识别“->”的基本行为。

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

4

3 回答 3

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

进一步扩展,您可能会感到困惑的是,在该语法中它说 type -> type,这意味着您可以在左侧有一个箭头。这很好,但它需要在括号中。这会有所帮助,也许看到以下内容会有所帮助。它帮助了我。

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)"
于 2010-03-08T19:58:39.617 回答
4

我认为您应该将左递归排除在语法之外。代替

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

你得到类似的东西

typeStart ::= identifier 
type ::= typeStart (-> type)?
identifier ::= [A-Za-z0-9.`]+ 

然后这将更容易直接翻译成秒差距,我想。(有人会认为这try会起作用,我希望它会以某种方式起作用,但是是的,我的经验也是,我必须至少在 Parsec 中达到齐腰深,然后才能理解“放在哪里try”才能使事情起作用。)

考虑查看F# 中的 Monadic Parser Combinators(以及之前的 7 个 C# 博客条目)以了解一些基础知识。我认为parsec 文档(尝试从上到下阅读它们,如果我没记错的话,它们是不错的)以及各种研究论文中的一些示例讨论了您问题中的问题。

于 2010-03-09T00:42:56.510 回答
0

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

于 2010-03-08T19:55:20.627 回答