区分逻辑与其他中缀运算符

时间:2012-02-09 17:44:22

标签: sql parsing f# parser-combinators fparsec

我正在尝试解析SQL搜索条件,并且无法让解析器将逻辑(ANDOR)与其他中缀运算符区分开来。我将它们解析为不同的节点(可能很难做到),但简化了评估阶段。这是相关的代码片段(如果需要,我可以包含更多内容)。

let opp = OperatorPrecedenceParser<_,_,_>()
let scalarExpr = opp.ExpressionParser
opp.TermParser <- constant <|> id <|> between lparen rparen scalarExpr <|> scalarExpr

//infix operators added here

let comparison = //(e.g., 1 < 2)
  let compareExpr = pipe3 scalarExpr compareOp scalarExpr (fun l op r -> Comparison(op, l, r))
  between lparen rparen compareExpr <|> compareExpr

let andTerm = pstringCI "and" .>> ws
let orTerm = pstringCI "or" .>> ws

let searchCondition, searchConditionRef = createParserForwardedToRef()
searchConditionRef := 
  [ comparison 
    pipe3 searchCondition andTerm searchCondition (fun l _ r -> And(l, r))
    pipe3 searchCondition orTerm searchCondition (fun l _ r -> Or(l, r))
    between lparen rparen searchCondition ]
  |> choice

let filter : Parser<_,unit> = ws >>. searchCondition .>> eof

"1 = 1"正确解析为Comparison (Eq,Constant (Int32 1),Constant (Int32 1))

但是一旦我尝试加入两个与逻辑运算符的比较,例如"1 = 1 or 2 = 2",就无法解析

  

Ln:1 Col:7中的错误   1 = 1或2 = 2
  ^
  期望:输入结束或中缀操作员
  :7

我希望它在错误之前解析1作为标量表达式,并在点击or回溯时,意识到它不是中缀运算符,返回1作为完整标量,并且认识到它正在解析由逻辑运算符or连接的条件的左侧。

相反,它似乎继续假设1开始一个更复杂的标量表达式,可能涉及一个中缀运算符。

代码是否有问题,或者是将AND / OR解析为中缀运算符(使用相同的OperatorPrecedenceParser)的解决方案?我宁愿不去那条路,所以我希望我在某个地方犯了一个简单的错误。

complete code正在上演。

3 个答案:

答案 0 :(得分:4)

我认为最终你会发现你需要将andor视为具有优先级规则的中缀运算符,因为这正是它们的原因,也是大多数解析器包括fparsec和fsyacc的原因所在处理它们的特殊功能(即通过优先级和关联性规则解决歧义)。

你发现有一个案例强调了这一点,但考虑另一个案例:

1 = 1 or 2 = 2 and 3 =3

应解析为(1 = 1 or 2 = 2) and 3 = 3还是1 = 1 or (2 = 2 and 3 = 3)

答案 1 :(得分:2)

您的解析器在第一个等式之后停止,因为choice的{​​{1}}组合器将第一个参数解析器searchCondition应用于输入,并且成功后只返回参数解析器的结果。然后,您会收到错误,因为comparison无法在filter之后解析eof

searchConditionchoice组合器实施最长匹配规则,并且在发生错误后他们回溯,如tutorial。所以你的<|>解析器无法工作。

另一个问题是你的searchCondition解析器是左递归的,因为第二个和第三个searchCondition参数将尝试再次应用choice而不先前消耗任何输入。左递归会导致堆栈溢出。

类似地,在searchCondition定义的末尾<|> scalarExpr是不必要的,并且可以导致无限递归。

当您将左递归解析器语法转换为FParsec时,您需要消除左递归。

修复opp.TermParser解析器的一种方法是分解左侧表达式:

searchCondition

甚至更简单:

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

do searchConditionRef:=
    let comparisonTerm =
        comparison <|> between lparen rparen searchCondition

    pipe2 comparisonTerm (many ((andTerm <|> orTerm) .>>. comparisonTerm)) 
          (fun l opRList -> 
                List.fold (fun l (op, r) -> op l r) l opRList)

更新的 在语法中,parens解析也存在问题,请参阅下面的注释。

答案 2 :(得分:1)

为逻辑运算符创建单独的OperatorPrecedenceParser似乎已修复它。

我替换了

let andTerm = pstringCI "and" .>> ws
let orTerm = pstringCI "or" .>> ws

let searchCondition, searchConditionRef = createParserForwardedToRef()
searchConditionRef := 
  [ comparison 
    pipe3 searchCondition andTerm searchCondition (fun l _ r -> And(l, r))
    pipe3 searchCondition orTerm searchCondition (fun l _ r -> Or(l, r))
    between lparen rparen searchCondition ]
  |> choice

let condOpp = OperatorPrecedenceParser()
let searchCondition = condOpp.ExpressionParser
condOpp.TermParser <- (attempt comparison) <|> between lparen rparen searchCondition <|> searchCondition
condOpp.AddOperator(InfixOperator("or", ws, 1, Assoc.Left, fun l r -> Or(l, r)))    
condOpp.AddOperator(InfixOperator("and", ws, 2, Assoc.Left, fun l r -> And(l, r)))    

(1 = 1 or 2 = 2) and 3 = 31 = 1 or (2 = 2 and 3 = 3)正确解析。