2009-02-08 8 views
11

Ich habe folgende EBNF, die ich analysieren möchte:EBNF zu Scala Parser combinator

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

Und das ist, was ich habe:

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

Aber ich würde gerne wissen, ob es eine ist besserer Weg, vorzugsweise ohne auf Casting zurückgreifen zu müssen (asInstanceOf).

Antwort

12

Ich würde es tun, wie folgt:

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

Verkürzte expressions-expr der Kürze halber als auch den Typ alias E aus dem gleichen Grund gegeben.

Der Trick, den ich hier verwende, um die hässliche Fallanalyse zu vermeiden, ist, einen Funktionswert aus der inneren Produktion zurückzugeben. Diese Funktion nimmt eine Expression (die die primary sein wird) und gibt dann eine neue Expression basierend auf der ersten zurück. Dies vereint die beiden Fälle von Punktversand und Klammerausdrücken. Schließlich wird die collapse-Methode verwendet, um die linearen List von Funktionswerten in einen richtigen AST zusammenzufassen, beginnend mit dem angegebenen primären Ausdruck.

Beachten Sie, dass LengthExpression nur als Wert (unter Verwendung ^^^) von seiner jeweiligen Produktion zurückgegeben wird. Dies funktioniert, weil die Begleitobjekte für Fallklassen (unter der Annahme, dass LengthExpression tatsächlich eine Fallklasse ist) den entsprechenden Funktionswert, der an ihren Konstruktor delegiert, erweitern. Die durch LengthExpression repräsentierte Funktion benötigt also eine einzige Expression und gibt eine neue Instanz LengthExpression zurück, die genau unseren Anforderungen für die Baumkonstruktion höherer Ordnung entspricht.