解析带括号的表达式

时间:2012-02-10 19:30:37

标签: f# fparsec

我有以下fsyacc语法(稍微修改过的)SQL搜索条件:

scalar_expr:
    | ID                                    { Identifier($1) }
    | constant                              { Constant($1) }
    | unary_op scalar_expr                  { Unary($1, $2) }
    | scalar_expr binary_op scalar_expr     { Binary($2, $1, $3) }
    | LPAREN scalar_expr RPAREN             { $2 }

search_condition:
    | search_condition OR search_condition  { Or($1, $3) }
    | search_condition AND search_condition { And($1, $3) }
    | scalar_expr comparison scalar_expr    { Comparison($2, $1, $3) }
    | LPAREN search_condition RPAREN        { $2 }

我已经在FParsec中重新实现了它(在previous question的帮助下)。以下是相关内容:

let binOpp = OperatorPrecedenceParser()
let scalarExpr = binOpp.ExpressionParser
binOpp.TermParser <- 
  [ constant 
    id
    between lparen rparen scalarExpr ]
  |> choice

// binary/unary ops added here

let comparison = 
  let compareExpr = pipe3 scalarExpr compareOp scalarExpr (fun l op r -> Comparison(op, l, r))
  between lparen rparen compareExpr <|> compareExpr

let andTerm = stringCIReturn "and" (fun l r -> And(l, r)) .>> ws
let orTerm = stringCIReturn "or" (fun l r -> Or(l, r)) .>> ws

let searchCondition, searchConditionRef = createParserForwardedToRef()
searchConditionRef:= 
  chainl1 comparison (andTerm <|> orTerm)        
  <|> between lparen rparen searchCondition

这解析1 = 1 or 2 = 2,但是在parens中包含常量或整个搜索条件会导致它失败(奇怪的是,在parens工作中包装比较)。这是一个失败的例子:

Error in Ln: 1 Col: 8
(1 = 1 or 2 = 2)
       ^
Expecting: infix operator or ')'
: 8

标量,比较和搜索条件都可以类似地开始(打开paren - &gt;常量 - &gt;中缀运算符),但基本上由最终遇到的运算符类型区分。例如,如果你点击or,你就会知道开头的paren属于整个条件,而不是左边的比较。这是通过回溯正确处理的吗?如果是这样,那么在以这种方式分析复杂表达式时,您将如何失败? 不消耗任何输入?

处理标量,比较和搜索条件的可选括号由fsyacc语法中的左递归处理。我知道这需要在FParsec中进行考虑。但是从上面的错误中,我无法想象如何摆脱广泛的回溯无论如何。

1 个答案:

答案 0 :(得分:3)

Meta:为什么FParsec标签不能解决这个问题?

我将引用自previous question页面上的评论:

嵌套但不是多次递归的表达式语法使得parens在这里解析有点讨厌。问题是当解析器在某些位置看到一个左括号时,它还不知道括号表达式是否需要被解析为scalarExprcomparisonsearchCondition。为了能够解析这样的表达式,你必须在打开括号之后和关闭括号之前为解析器错误引入一些有限的回溯,以便解析器可以暂时用一个子语法解析带括号的表达式,并且如果需要,再用另一个语法解析。

let tryBetweenParens p = lparen >>? (p .>>? rparen)

let opp = OperatorPrecedenceParser<_,_,_>()
let scalarExpr = opp.ExpressionParser
opp.TermParser <- choice [constant; id; tryBetweenParens scalarExpr]

// ... 

let comparison = // doesn't currently allow chained comparisons ( e.g. 1 = 2 = 3)
    let compareExpr = pipe3 scalarExpr compareOp scalarExpr (fun l op r -> Comparison(op, l, r))
    compareExpr <|> tryBetweenParens compareExpr

// ...

let searchCondition, searchConditionRef = createParserForwardedToRef()
do searchConditionRef:= 
    chainl1 (comparison <|> between lparen rparen searchCondition) 
            (andTerm <|> orTerm)

完整代码位于http://pastebin.com/i7JFJWJE

使用通常的表达式语法,其中任何带括号的(顶级)表达式在叶子术语有效的任何地方都是有效的,解析显然更简单,因为你只需要在语法的一个地方处理parens。正如史蒂芬斯文森所建议的那样,这只是使用单OperatorPrecedenceParser的另一个论点。但是,如果希望在解析后能够生成良好的错误消息,则必须使用源位置对AST进行注释。