我有以下要解析的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).
我会这样做:
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
,精确满足我们对高阶树结构的需求.