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).

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