【问题标题】:Parser the call of a function - FParsec解析函数的调用 - FParsec
【发布时间】:2019-02-02 22:50:06
【问题描述】:

我尝试解析一个函数的调用,这里是变种:

add 8 2
add x y
add (inc x) (dec y)
funcWithoutArgs

根据我在代码中分布分析器的方式,也许还有它们的编码方式,我会得到错误,以及成功但不需要的分析。 例如,这个:

add 4 7

返回以下 AST:

[Call ("foo",[Number 4]);
 Number 7]

因此他只取第一个参数。

当我这样做时:

foo x y

他把这个 AST 发回给我:

[Call ("foo",[Call ("x",[Call ("y",[])])])]

这不是我想要的,因为在这里,每个参数都调用下一个作为参数。

另一个例子,当我这样做时:

foo x y
inc x

我明白了:

[Call ("foo",[Call ("x",[Call ("y",[Call ("inc",[Call ("x",[])])])])])]

它的作用与上面相同,但也调用该行后面的代码。当我向我的分析器询问新行时(请参阅代码),它会向我发送以下信息:

[Call ("foo",[]); Call ("x",[]); Call ("y",[]); Call ("inc",[]); Call ("x",[])]

即使在括号中也不起作用:

foo (x) (y)

给:

[Call ("foo",[]); Call ("x",[]); Call ("y",[])]

还有:

add (inc x) (dec y)

给:

Error in Ln: 1 Col: 1
Note: The error occurred on an empty line.

The parser backtracked after:
  Error in Ln: 2 Col: 5
  add (inc x) (dec y)
      ^
  Expecting: end of input or integer number (32-bit, signed)

  The parser backtracked after:
    Error in Ln: 2 Col: 10
    add (inc x) (dec y)
             ^
    Expecting: ')'

[]

简而言之,我的函数调用分析器无法正常工作。每次我更改某些内容时,例如换行、尝试或不同的层次结构,有些东西不起作用…… 你知道如何解决这个非常烦人的问题吗?

这是使用的最少功能代码:

open FParsec

// Ast

type Expression =
    | Number of int
    | Call of string * Expression list

type Program = Expression list

// Tools

let private bws p =
    spaces >>? p .>>? spaces

let private suiteOf p =
    sepEndBy p spaces1

let inline private betweenParentheses p label =
    between (pstring "(") (pstring ")") p
    <?> (label + " between parentheses")

let private identifier =
    many1Satisfy2 isLetter (fun c -> isLetter c)

// Expressions

let rec private call = parse {
        let! call = pipe2 (spaces >>? identifier) (spaces >>? parameters)
                        (fun id parameters -> Call(id, parameters)) // .>>? newline
        return call
    }

and private parameters = suiteOf expression

and private callFuncWithoutArgs =
    identifier |>> fun id -> Call(id, [])

and private number = pint32 |>> Number

and private betweenParenthesesExpression =
    parse { let! ex = betweenParentheses expression "expression"
            return ex }

and private expression =
    bws (attempt betweenParenthesesExpression <|>
         attempt number <|>
         attempt call <|>
         callFuncWithoutArgs)

// -------------------------------

let parse code =
    let parser = many expression .>>? eof

    match run parser code with
        | Success(result, _, _) -> result
        | Failure(msg, _, _) ->
            printfn "%s" msg
            []

System.Console.Clear()

parse @"
add 4 7

foo x y

inc x

foo (x) (y)

add (inc x) (dec y)

" |> printfn "%A"

【问题讨论】:

  • 第一个问题:为什么要将funcWithoutArgs 解析为函数调用而不是标识符?在我所知道的每一种语言中,调用函数都需要与引用函数不同的语法:例如,func() 是函数调用,而 func 只是对函数的引用。我认为您的问题的一部分(尽管不是全部)可能源于 funcWithoutArgs 正在解析为函数调用这一事实。
  • 第二个问题:您声明 [Call ("foo",[Call ("x",[Call ("y",[])])])] 不是您希望 foo x y 解析为的内容,但我无法从您示例中的类型判断您 想要foo x y 解析到。我假设您希望它解析为[Call ("foo", [Identifier "x"; Identifier "y"])] 之类的东西,但在您的精简示例中,DU 中没有Identifier。那么你希望foo x y 实际解析到什么?你真正想要的结果是什么?
  • @rmunn 第一个答案:funcWithoutArgs 顾名思义,应该是一个函数调用,没有参数,实际上可以看作是一个简单的标识符。例如,在 OCaml、Haskell 甚至 F# 中,据我所知,没有区别,因为变量/数据只是可以应用的数据。这就是我一直试图复制的。你会建议什么?也许要创建一个新的Variable 节点?
  • 第二个答案:其实foo x y应该是这样分析的:[call ("foo",[Call ("x", []); call ("y", []))]。按照我上面说的。

标签: f# fparsec


【解决方案1】:

您的主要问题是您的解析器的高级设计错误。

您当前的设计是一个表达式可以是:

  1. 括号之间的表达式(可以说是“子表达式”)(这里没问题)
  2. 一个数字(这里没问题)
  3. 带参数的调用,它是一个标识符,后跟一个以空格分隔的表达式列表(这是问题的主要部分)
  4. 没有参数的调用,它是单个标识符(这会导致问题)

看看表达式foo x y,让我们按照解析器的顺序应用这些规则。没有括号,foo 不是数字,所以它不是 3 就是 4。首先我们尝试 3。foo 后跟 x yx y 是否解析为表达式?为什么,是的,确实如此:它解析为带参数的调用,其中x 是函数,y 是参数。由于 x y 匹配 3,它根据规则 3 进行解析而不检查规则 4,因此 foo x y 匹配 foo (x y) 将:使用单个参数调用 foo,即使用单个参数调用 x参数y

如何解决这个问题?好吧,您可以尝试交换 3 和 4 的顺序,以便在带参数的调用之前检查不带参数的函数调用(这将使 x y 解析为 x。但这会失败,因为 foo x y将与 foo 匹配。因此将规则 4 放在规则 3 之前在这里不起作用。

真正的解决方案是将表达式的规则分成两个级别。我称之为“价值”的“内在”层次可能是:

  1. 括号之间的表达式
  2. 一个数字
  3. 不带参数的函数调用

而“外”层,即表达式的解析规则,将是:

  1. 一个函数调用个参数,都是not表达式
  2. 一个值

请注意,这些解析级别是相互递归的,因此您需要在实现中使用createParserForwardedToRef。让我们看看foo x y 将如何使用这种设计进行解析:

首先,foo 解析为标识符,因此请检查它是否可能是带参数的函数调用。 x 是否解析为一个值?是的,根据价值规则 3。 y 是否解析为一个值?是的,根据价值规则 3。所以foo x y解析为函数调用。

现在funcWithoutParameters 呢?它会失败表达式规则 1,因为它后面没有参数列表。所以它会检查表达式规则 2,然后它会根据值规则 3 匹配。

好的,对伪代码的基本完整性检查有效,所以让我们将其转换为代码。但首先,我将在您的解析器中提到我尚未提及的other 问题,即您没有意识到 FParsec spaces 解析器 also matches newlines。因此,当您将 expression 解析器包装在 bws(“空格之间”)中时,它也会在解析的文本之后使用换行符。因此,当您解析以下内容时:

foo a b
inc c

suiteOf expression 看到列表a b inc c 并将所有这些转换为foo 的参数。在下面的代码中,我区分了 FParsec 的 spaces 解析器(包括换行符)和解析水平空格(空格和制表符但不是换行符)的解析器,在适当的位置使用每个。以下代码实现了我在此答案中提到的设计,并且对于您编写的所有测试表达式,它的输出对我来说都是正确的:

open FParsec

// Ast

type Expression =
    | Number of int
    | Call of string * Expression list

type Program = Expression list

// Tools

let private justSpaces  = skipMany  (pchar ' ' <|> pchar '\t')
let private justSpaces1 = skipMany1 (pchar ' ' <|> pchar '\t')

let private bws p =
    spaces >>? p .>>? spaces

let private suiteOf p =
    sepEndBy1 p (justSpaces1)

let inline private betweenParentheses p label =
    between (pstring "(") (pstring ")") p
    <?> (label + " between parentheses")

let private identifier =
    many1Satisfy2 isLetter (fun c -> isLetter c)

// Expressions

let private expression, expressionImpl = createParserForwardedToRef()

let private betweenParenthesesExpression =
    parse { let! ex = betweenParentheses expression "expression"
            return ex }

let private callFuncWithoutArgs =
    (identifier |>> fun id -> Call(id, []))

let private number = pint32 |>> Number

let private value =
    justSpaces >>? (attempt betweenParenthesesExpression <|>
                    attempt number <|>
                    callFuncWithoutArgs)

let private parameters = suiteOf value

let rec private callImpl = parse {
        let! call = pipe2 (justSpaces >>? identifier) (justSpaces >>? parameters)
                          (fun id parameters -> Call(id, parameters))
        return call }

let call = callImpl

expressionImpl.Value <-
    bws (attempt call <|>
         value)

// -------------------------------

let parse code =
    let parser = many expression .>>? (spaces >>. eof)

    match run parser code with
        | Success(result, _, _) -> result
        | Failure(msg, _, _) ->
            printfn "%s" msg
            []

System.Console.Clear()

parse @"
add 4 7

foo x y

inc x

foo (x) (y)

add (inc x) (dec y)
" |> printfn "%A"

附:我使用了http://www.quanttec.com/fparsec/users-guide/debugging-a-parser.html 建议的以下运算符,极大地帮助我追踪问题:

let (<!>) (p: Parser<_,_>) label : Parser<_,_> =
    fun stream ->
        printfn "%A: Entering %s" stream.Position label
        let reply = p stream
        printfn "%A: Leaving %s (%A)" stream.Position label reply.Status
        reply

用法:把let parseFoo = ...变成let parseFoo = ... &lt;!&gt; "foo"。然后,您将在控制台中获得如下所示的调试输出流:

(Ln: 2, Col: 20): Entering expression
(Ln: 3, Col: 1): Entering call
(Ln: 3, Col: 5): Entering parameters
(Ln: 3, Col: 5): Entering bwParens
(Ln: 3, Col: 5): Leaving bwParens (Error)
(Ln: 3, Col: 5): Entering number
(Ln: 3, Col: 6): Leaving number (Ok)
(Ln: 3, Col: 7): Entering bwParens
(Ln: 3, Col: 7): Leaving bwParens (Error)
(Ln: 3, Col: 7): Entering number
(Ln: 3, Col: 8): Leaving number (Ok)
(Ln: 3, Col: 8): Leaving parameters (Ok)
(Ln: 3, Col: 8): Leaving call (Ok)
(Ln: 3, Col: 8): Leaving expression (Ok)

当您试图弄清楚为什么您的解析器没有按照您的预期执行时,这会很有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多