【问题标题】:Parsing parenthesized expressions解析带括号的表达式
【发布时间】:2012-02-10 19:30:37
【问题描述】:

对于 SQL 搜索条件,我有以下 fsycc 语法(稍作修改的形式):

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,但是在括号中包装一个常量或整个搜索条件会导致它失败(奇怪的是,在括号中包装比较有效)。这是一个失败的例子:

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

标量、比较和搜索条件可能都以相似的方式开始(开括号 -> 常量 -> 中缀运算符),但本质上是根据最终遇到的运算符类型来区分的。例如,如果您点击or,您就知道左括号属于整个条件,而不是左侧的比较。这是否通过回溯正确处理?如果是这样,您将如何以这种方式失败 - 在解析复杂表达式时 不消耗任何输入?

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

【问题讨论】:

    标签: f# fparsec


    【解决方案1】:

    元:为什么 FParsec 标签在这个问题上不起作用?

    我将从页面上的 cmets 中引用自己的 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

    使用通常的表达式语法,其中任何带括号的(顶级)表达式在叶项有效的任何地方都有效,解析显然更简单,因为您只需要在语法中的一个位置处理括号。正如 Stephen Swensen 所建议的,这是仅使用单个 OperatorPrecedenceParser 的另一个论据。但是,如果您希望能够在解析后生成良好的错误消息,则必须使用源位置注释 AST。

    【讨论】:

    • 回答“元”问题:自从我发布问题以来,我一直无法编辑它,所以我尝试删除“fparsec”标签(作为测试)并且它有效。不幸的是,我无法将其添加回来(或进行任何其他编辑)。我昨天标记了它以供版主注意。还在等待。
    • 其次,感谢您的周到回复。我要到星期一才能试一试。我有一个使用优先解析器的工作解决方案,我在上一个问题中发布了该解决方案。您的代码处理 ANDOR 的优先级是否不同?
    • AND 和 OR 运算符在您的原始问题中也具有相同的优先级。您可以通过让 OR 解析器调用 AND 解析器来处理不同的优先级,例如 exprstackoverflow.com/questions/4559399/#answer-4567275 中调用 term 尽管那时使用 OPP 可能更容易。在任何情况下,您都必须允许回溯以启用在不同语法级别的括号解析。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    • 2015-12-30
    • 2014-11-27
    相关资源
    最近更新 更多