I have the following EBNF that I want to parse:
PostfixExp -> PrimaryExp ( '[' Exp ']' | . id '(' ExpList ')' | . length )*
And this is what I got:
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) } ) })
But I would like to know if there is a better way, preferably without having to resort to casting (asInstanceOf).
I would do it like this:
Shortened
expressionstoexprfor brevity as well as added the type aliasEfor the same reason.The trick that I’m using here to avoid the ugly case analysis is to return a function value from within the inner production. This function takes an
Expression(which will be theprimary) and then returns a newExpressionbased on the first. This unifies the two cases of dot-dispatch and bracketed expressions. Finally, thecollapsemethod is used to merge the linearListof function values into a proper AST, starting with the specified primary expression.Note that
LengthExpressionis just returned as a value (using^^^) from its respective production. This works because the companion objects for case classes (assuming thatLengthExpressionis indeed a case class) extend the corresponding function value delegating to their constructor. Thus, the function represented byLengthExpressiontakes a singleExpressionand returns a new instance ofLengthExpression, precisely satisfying our needs for the higher-order tree construction.