Ответ 1
Я бы сделал это следующим образом:
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
, точно удовлетворяющий нашим потребностям для построения дерева более высокого порядка.