F# 解析函数的调用-FParsec

F# 解析函数的调用-FParsec,f#,fparsec,F#,Fparsec,我尝试解析函数的调用,以下是变量: add 8 2 add x y add (inc x) (dec y) funcWithoutArgs 根据我在代码中如何分配分析器,以及它们的编码方式,我会得到错误,以及成功但不需要的分析。 例如,这: add 4 7 返回以下值: [Call ("foo",[Number 4]); Number 7] 因此,他只接受第一个参数 当我这样做时: foo x y 他今天早上把我送回来: [Call ("foo",[Call ("x",[Call ("

我尝试解析函数的调用,以下是变量:

add 8 2
add x y
add (inc x) (dec y)
funcWithoutArgs
根据我在代码中如何分配分析器,以及它们的编码方式,我会得到错误,以及成功但不需要的分析。 例如,这:

add 4 7
返回以下值:

[Call ("foo",[Number 4]);
 Number 7]
因此,他只接受第一个参数

当我这样做时:

foo x y
他今天早上把我送回来:

[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)
给出:

以及:

给出:

简而言之,我的函数调用分析器不能正常工作。每次我改变一些东西,比如一条新的线路,一次尝试,或者一个不同的层次结构,一些东西都不起作用。。。 你知道如何解决这个非常烦人的问题吗

以下是使用的最低功能代码:

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"
打开FParsec
//Ast
类型表达式=
|整数的数目
|字符串*表达式列表的调用
类型程序=表达式列表
//工具
让私人bws p=
空间>>?p.>>?空间
让私人套房=
sepEndBy p空间1
让内联private在parentheses p标签之间=
在(pstring)之间
(标签+“括号之间”)
让私有标识符=
许多令人满意的2胰岛(乐趣c->胰岛c)
//表情
让rec private call=parse{
let!call=pipe2(空格>>?标识符)(空格>>?参数)
(趣味id参数->调用(id,参数))/。>>?换行符
回电
}
and private parameters=suiteOf expression
和私用callFuncWithoutArgs=
标识符|>>乐趣id->呼叫(id,[])
和私人号码=pint32 |>>号码
和private之间的表达式=
解析{let!ex=betweenParentheses表达式“expression”
返回值}
和私人表达=
bws(在ParentHesseExpression之间的尝试
尝试号码
尝试呼叫
callFuncWithoutArgs)
// -------------------------------
让我们分析代码=
让解析器=多个表达式。>>?eof
将运行解析器代码与
|成功(结果)->result
|失败(消息,消息)->
printfn“%s”消息
[]
System.Console.Clear()
解析@”
加4 7
富x y
公司x
傅(x)(y)
加上(包括x)(12月y日)
|>printfn“%A”

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

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

  • 括号之间的表达式(可以说是“子表达式”)(这里没有问题)
  • 一个号码(这里没问题)
  • 带有参数的调用,该参数是一个标识符,后跟一个以空格分隔的表达式列表(这是问题的主要部分)
  • 一个没有参数的调用,它是一个单一标识符(这会导致问题)
  • 查看表达式
    fooxy
    ,让我们按照解析器的顺序应用这些规则。没有括号,
    foo
    不是数字,所以它不是3就是4。首先,我们尝试3
    foo
    后跟
    xy
    :是否将
    xy
    解析为表达式?为什么,是的,它是这样做的:它解析为带有参数的调用,
    x
    是函数,
    y
    是参数。由于
    xy
    匹配3,它根据规则3进行解析,而不检查规则4,因此
    fooxy
    匹配类似
    foo(xy)
    将:使用单个参数调用
    foo
    ,这是使用参数
    y
    调用
    x

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

    真正的解决方案是将表达式的规则分为两个级别。“内部”级别,我称之为“值”,可以是:

  • 括号之间的表达式
  • 没有参数的函数调用
  • “外部”级别,即表达式的解析规则是:

  • 带有参数的函数调用,所有参数都是值,而不是表达式
  • 价值
  • 请注意,这些解析级别是相互递归的,因此您需要在实现中使用
    createParserForwardedToRef
    。让我们看看如何使用此设计解析
    foo x y

    首先,
    foo
    解析为一个标识符,因此检查它是否可能是一个带有参数的函数调用。
    x
    解析为一个值吗?是的,在值的规则3下。
    y
    解析为一个值吗?是的,在值的规则3下。
    fooxy
    解析为一个函数调用

    现在如何处理
    funcWithoutParameters
    ?它将使表达式规则1失败,因为它后面没有参数列表。因此将检查表达式规则2,然后根据值规则3进行匹配

    好的,对伪代码进行基本的健全性检查是可行的,所以让我们把它转换成代码。但是首先,我要提到解析器中的另一个问题,我还没有提到,那就是您没有意识到FParsec
    空格
    解析器。所以当您将
    表达式
    解析器包装在
    bws
    (“空格之间”)中时,它还将在解析文本后使用换行符。因此,当您解析以下内容时:

    foo a b
    inc c
    
    suiteOf expression
    查看列表
    a b inc c
    ,并将所有这些转换为
    foo
    的参数
    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"
    
    foo a b
    inc c
    
    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"
    
    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
    
    (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)