问题 EBNF到Scala解析器组合器


我有以下要解析的EBNF:

PostfixExp      -> PrimaryExp ( "[" Exp "]" 
                                | . id "(" ExpList ")" 
                                | . length )*

这就是我得到的:

def postfixExp: Parser[Expression] = (
    primaryExp ~ rep(
        "[" ~ expression ~ "]"
        | "." ~ ident ~"(" ~ repsep(expression, "," ) ~ ")" 
        | "." ~ "length") ^^ {
        case primary ~ list =>  list.foldLeft(primary)((prim,post) =>
                post match {
                    case "[" ~ length ~ "]" => ElementExpression(prim, length.asInstanceOf[Expression])
                    case "." ~ function ~"(" ~ arguments ~ ")" =>  CallMethodExpression(prim, function.asInstanceOf[String], arguments.asInstanceOf[List[Expression]])
                    case _ => LengthExpression(prim)
                }
            )
    })

但我想知道是否有更好的方法,最好不必诉诸铸造(asInstanceOf)。


8473
2018-02-08 16:19


起源



答案:


我会这样做:

type E = Expression

def postfixExp = primaryExp ~ rep(
    "[" ~> expr <~ "]" ^^ { e => ElementExpression(_:E, e) }
  | "." ~ "length" ^^^ LengthExpression
  | "." ~> ident ~ ("(" ~> repsep(expr, ",") <~ ")") ^^ flatten2 { (f, args) =>
      CallMethodExpression(_:E, f, args)
    }
) ^^ flatten2 { (e, ls) => collapse(ls)(e) }

def expr: Parser[E] = ...

def collapse(ls: List[E=>E])(e: E) = {
  ls.foldLeft(e) { (e, f) => f(e) }
}

缩短 expressions 至 expr 为了简洁以及添加类型别名 E 为了同样的原因。

我在这里用来避免丑陋案例分析的诀窍是返回一个 功能价值 来自内部生产。这个功能需要一个 Expression (将是 primary)然后返回一个新的 Expression 基于第一个。这统一了两个点分派和括号表达式的情况。最后, collapse 方法用于合并线性 List 将函数值转换为适当的AST,从指定的主表达式开始。

注意 LengthExpression 只是作为一个值返回(使用 ^^^)来自各自的生产。这是因为case类的伴随对象(假设为 LengthExpression 确实是一个case类)扩展委托给它们的构造函数的相应函数值。因此,所代表的功能 LengthExpression 需要一个 Expression 并返回一个新的实例 LengthExpression,正是满足了我们对高阶树构造的需求。


12
2018-02-08 22:30