2016-04-08 13 views
3

Wenn ich diesen Test ausführen:Scala Combinators JavaTokennnParsers || ' erwartet, aber ‚>‘ gefunden

import com.dvMENTALmadness.parsers.{BinaryOp, ExprType, Number, SimpleEquationParser} 
import org.scalatest.FlatSpec 

class SimpleEquationParserTest extends FlatSpec { 
    "(1+2) > (3+4)" should " == false" in { 
    val result = SimpleEquationParser("(1+2) > (3+4)") 
    println(result) 
    assert(result == Right(BinaryOp(">",BinaryOp("+",Number(1.0),Number(2.0),ExprType.Num),BinaryOp("+",Number(3.0),Number(4.0),ExprType.Num),ExprType.Bool))) 
    } 
} 

ich:

Left("`||' expected but `>' found") did not equal Right(BinaryOp(>,BinaryOp(+,Number(1.0),Number(2.0),Num),BinaryOp(+,Number(3.0),Number(4.0),Num),Bool)) 

Wenn ich den Ausdruck ((1+2) > (3+4)) ändern es funktioniert, aber ich brauche die optional unterstützen zu können, Klammern. Irgendwelche Vorschläge? Im Folgenden sind die Klassendefinition sowie die Log-Spur:

package com.dvMENTALmadness.parsers 

import com.dvMENTALmadness.parsers.ExprType.ExprType 
import scala.util.parsing.combinator.{JavaTokenParsers, PackratParsers} 

sealed trait Expr 
sealed trait Var extends Expr { 
    def key: String 
} 
object ExprType extends Enumeration { 
    type ExprType = Value 
    val Var, Num, Text, Bool = Value 
} 

case class Text(value: String) extends Expr 
case class Number(value: Double) extends Expr 
case class Bool(value: Boolean) extends Expr 
case class NumericVar(key: String) extends Var 
case class TextVar(key: String) extends Var 
case class BoolVar(key: String) extends Var 
case class AnyVar(key: String) extends Var 
case class UnaryOp(operator: String, arg: Expr, expType: ExprType) extends Expr 
case class BinaryOp(operator: String, left : Expr, right: Expr, expType: ExprType) extends Expr 

trait ExprParser extends JavaTokenParsers with PackratParsers { 

    def foldExpr(etype: ExprType)(pat: Expr ~ List[String ~ Expr]) : Expr = pat match { 
    case left ~ xs => xs.foldLeft(left)((left, acc) => acc match { 
     case op ~ right => BinaryOp(op, left, right, etype) 
    }) 
    } 

    // see: http://jim-mcbeath.blogspot.com/2011/07/debugging-scala-parser-combinators.html 
    implicit def toLogged(name: String) = new { 
    def !!![T](p:Parser[T]) = log(p)(name) // for debugging 
    //def !!![T](p:Parser[T]) = p   // for production 
    } 
} 

trait BoolParser extends ExprParser { 

    // Operator precedence: http://www.tutorialspoint.com/scala/scala_operators.htm 
    def expr = "expr" !!! bool_expr | num_expr | text_expr 
    def bool_expr = "bool_expr" !!! or | bool_term 
    def num_expr = "num_expr" !!! num_equality | num_term 
    def text_expr = "text_expr" !!! text_equality 

    // operations 
    def or = "or" !!! and ~ rep("||" ~ and) ^^ foldExpr(ExprType.Bool) 
    def and = "and" !!! equality ~ rep("&&" ~ equality) ^^ foldExpr(ExprType.Bool) 

    def equality = "equality" !!! bool_equality | num_equality | text_equality 
    def bool_equality = "bool_equality" !!! bool_term ~ rep("==" ~ bool_term | "!=" ~ bool_term) ^^ foldExpr(ExprType.Bool) 
    def num_equality = "num_equality" !!! relational ~ rep("==" ~ relational | "!=" ~ relational) ^^ foldExpr(ExprType.Num) 
    def text_equality = "text_equality" !!! concat ~ rep("==" ~ concat | "!=" ~ concat) ^^ foldExpr(ExprType.Text) 

    def relational = "relational" !!! additive ~ rep(">=" ~ additive | "<=" ~ additive | ">" ~ additive | "<" ~ additive) ^^ foldExpr(ExprType.Num) 
    def additive = "additive" !!! multiplicative ~ rep("+" ~ multiplicative | "-" ~ multiplicative) ^^ foldExpr(ExprType.Num) 
    def multiplicative = "multiplicative" !!! num_term ~ rep("*" ~ num_term | "/" ~ num_term | "%" ~ num_term) ^^ foldExpr(ExprType.Num) 
    def concat = "concat" !!! text ~ rep("+" ~ text) ^^ foldExpr(ExprType.Text) 

    def operators = "*" | "/" | "%" | "+" | "-" | "&&" | "||" 

    // terms 
    def term = "term" !!! bool_term | num_term 
    def bool_term = "bool_term" !!! bool | bool_parens | not 
    def num_term = "num_term" !!! num | num_parens | neg 

    def not:PackratParser[Expr] = "not" !!! "!" ~> bool_term ^^ (x => UnaryOp("!", x, ExprType.Bool)) 
    def neg:PackratParser[Expr] = "neg" !!! "-" ~> num_term ^^ (x => UnaryOp("-", x, ExprType.Num)) 

    def parens:PackratParser[Expr] = "parens" !!! "(" ~> expr <~ ")" 
    def bool_parens:PackratParser[Expr] = "bool_parens" !!! "(" ~> bool_expr <~ ")" 
    def num_parens:PackratParser[Expr] = "num_parens" !!! "(" ~> num_expr <~ ")" 
    def text_parens:PackratParser[Expr] = "text_parens" !!! "(" ~> text_expr <~ ")" 

    //values 
    def bool: PackratParser[Expr] = "bool" !!! 
    "true" ^^^ (Bool(true)) | 
    "false" ^^^ (Bool(false)) | 
    var_factor 

    def num: PackratParser[Expr] = "num" !!! 
    floatingPointNumber ^^ (x => Number(x.toDouble)) | 
    wholeNumber ^^ (x => Number(x.toDouble)) | 
    var_factor 

    def text: PackratParser[Expr] = "text" !!! 
    stringLiteral ^^ (x => Text(stripQuote(x))) | 
    var_factor 

    def var_factor: Parser[Expr] = "var_factor" !!! 
    id <~ ".asNumber" ^^ (x => NumericVar(x)) | 
    id <~ ".asText" ^^ (x => TextVar(x)) | 
    id <~ ".asBool" ^^ (x => BoolVar(x)) | 
    id ^^ (x => AnyVar(x)) 

    def id: PackratParser[String] = "id" !!! opt("{") ~> ident <~ opt("}") 

    private def stripQuote(s: String) = { 
    s.substring(1, s.length - 1) 
    } 
} 

object SimpleEquationParser extends BoolParser { 
    def apply(input: String) : Either[String,Expr] = { 

    parseAll("root" !!! expr, input) match { 
     case Success(r, _) => Right(r) 
     case Failure(msg, _) => Left(msg) 
     case Error(msg, _) => Left(msg) 
    } 
    } 
} 

Die Log-Spur:

trying root at [email protected]004 
trying expr at [email protected]004 
trying bool_expr at [email protected]004 
trying or at [email protected]004 
trying and at scala.util.parsin[email protected] 
trying equality at [email protected]004 
trying bool_equality at [email protected]004 
trying bool_term at [email protected]004 
trying bool at [email protected]004 
bool --> [1.1] failure: `true' expected but `(' found 

(1+2) > (3+4) 
^ 
trying var_factor at [email protected]004 
trying id at [email protected]004 
id --> [1.1] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `(' found 

(1+2) > (3+4) 
^ 
var_factor --> [1.1] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `(' found 

(1+2) > (3+4) 
^ 
trying id at [email protected]004 
id --> [1.1] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `(' found 

(1+2) > (3+4) 
^ 
trying id at [email protected]004 
id --> [1.1] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `(' found 

(1+2) > (3+4) 
^ 
trying id at [email protected]004 
id --> [1.1] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `(' found 

(1+2) > (3+4) 
^ 
bool_term --> [1.1] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `(' found 

(1+2) > (3+4) 
^ 
trying bool_parens at [email protected]004 
trying bool_expr at sca[email protected]59d016c9 
trying or at sca[email protected]59d016c9 
trying and at sca[email protected]59d016c9 
trying equality at sca[email protected]59d016c9 
trying bool_equality at sca[email protected]59d016c9 
trying bool_term at sca[email protected]59d016c9 
trying bool at sca[email protected]59d016c9 
bool --> [1.2] failure: `true' expected but `1' found 

(1+2) > (3+4) 
^ 
trying var_factor at sca[email protected]59d016c9 
trying id at sca[email protected]59d016c9 
id --> [1.2] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `1' found 

(1+2) > (3+4) 
^ 
var_factor --> [1.2] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `1' found 

(1+2) > (3+4) 
^ 
trying id at sca[email protected]59d016c9 
id --> [1.2] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `1' found 

(1+2) > (3+4) 
^ 
trying id at sca[email protected]59d016c9 
id --> [1.2] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `1' found 

(1+2) > (3+4) 
^ 
trying id at sca[email protected]59d016c9 
id --> [1.2] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `1' found 

(1+2) > (3+4) 
^ 
bool_term --> [1.2] failure: string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but `1' found 

(1+2) > (3+4) 
^ 
trying bool_parens at scala.util.parsing.combinator.PackratParsers$Pa[email protected] 
bool_parens --> [1.2] failure: `(' expected but `1' found 

(1+2) > (3+4) 
^ 
trying not at sca[email protected]59d016c9 
not --> [1.2] failure: `!' expected but `1' found 

(1+2) > (3+4) 
^ 
bool_equality --> [1.2] failure: `!' expected but `1' found 

(1+2) > (3+4) 
^ 
equality --> [1.2] failure: `!' expected but `1' found 

(1+2) > (3+4) 
^ 
trying num_equality at sca[email protected]59d016c9 
trying relational at sca[email protected]59d016c9 
trying additive at sca[email protected]59d016c9 
trying multiplicative at sca[email protected]59d016c9 
trying num_term at sca[email protected]59d016c9 
trying num at sca[email protected]59d016c9 
num --> [1.3] parsed: 1 
num_term --> [1.3] parsed: Number(1.0) 
multiplicative --> [1.3] parsed: (Number(1.0)~List()) 
trying multiplicative at sca[email protected]36c88a32 
trying num_term at sca[email protected]36c88a32 
trying num at sca[email protected]36c88a32 
num --> [1.5] parsed: 2 
num_term --> [1.5] parsed: Number(2.0) 
multiplicative --> [1.5] parsed: (Number(2.0)~List()) 
additive --> [1.5] parsed: (Number(1.0)~List((+~Number(2.0)))) 
relational --> [1.5] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
num_equality --> [1.5] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
and --> [1.5] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
or --> [1.5] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
bool_expr --> [1.5] parsed: BinaryOp(+,Number(1.0),Number(2.0),Num) 
bool_parens --> [1.6] parsed: BinaryOp(+,Number(1.0),Number(2.0),Num) 
bool_equality --> [1.6] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
equality --> [1.6] parsed: BinaryOp(+,Number(1.0),Number(2.0),Num) 
and --> [1.6] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
or --> [1.6] parsed: (BinaryOp(+,Number(1.0),Number(2.0),Num)~List()) 
bool_expr --> [1.6] parsed: BinaryOp(+,Number(1.0),Number(2.0),Num) 
expr --> [1.6] parsed: BinaryOp(+,Number(1.0),Number(2.0),Num) 
root --> [1.6] parsed: BinaryOp(+,Number(1.0),Number(2.0),Num) 
Left(`||' expected but `>' found) 
+0

Beachten Sie, dass '(1 + 2)' mit 'bool_expr' geparst wird:' bool_parens -> [1.6] geparst: BinaryOp (+, Number (1.0), Number (2.0), Num) '. Aber es sollte mit einem 'num_expr' geparst werden, damit der erforderliche' relationale' Parser berücksichtigt werden kann. – Kolmar

+0

Auch glaube ich, dass einige Arten in 'foldExpr' falsch sind. Z.B. in 'num_equality' sollte das Ergebnis' ExprType.Bool' sein, aber es hat '^^ foldExpr (ExprType.Num)'. – Kolmar

+0

@Kolmar Ich konnte es funktionieren, indem Sie die Reihenfolge der Gleichheits-Parser so vertauschen, dass 'num_equality' vor' bool_equality' steht. Aber wenn ich das tat, wurden "wahr" und "falsch" als "ident" -Werte anstelle von reservierten booleschen Schlüsselwörtern geparst. Also habe ich einen 'reservierten' Parser hinzugefügt, um das zu korrigieren. Ich glaube, das entspricht Ihrer Empfehlung. –

Antwort

0

@Kolmar richtig ist, dass die Eingabe mit num_expr analysiert werden muss. Die Lösung unten ist, was ich mir ausgedacht habe, aber es fühlt sich weniger robust an, als ich es möchte. Das Problem läuft auf den Vorrang hinaus, aber ich hatte gehofft, dass es eine Möglichkeit gäbe, den Parser weiter zu überprüfen, nachdem der boolesche Zweig fehlgeschlagen ist. Stattdessen tauschte ich die Reihenfolge der equality Parser num_equality vor bool_equality zu überprüfen:

def equality = "equality" !!! num_equality | bool_equality | text_equality 

Aber weil true und false Schlüsselwörter definiert werden in der boolean Zweig, der nach numerischen Werten ausgewertet wird, wurden sie als Typ analysiert AnyVar statt Bool. Um dies zu beheben, dass ich einen reserved Parser hinzugefügt und verändert die var_factor auf die folgenden:

def var_factor: Parser[Expr] = "var_factor" !!! 
    id <~ ".asNumber" ^^ (x => NumericVar(x)) | 
    id <~ ".asText" ^^ (x => TextVar(x)) | 
    id <~ ".asBool" ^^ (x => BoolVar(x)) | 
    not(reserved) ~> id ^^ (x => AnyVar(x)) 

def id: PackratParser[String] = "id" !!! opt("{") ~> ident <~ opt("}") 
def reserved: Parser[String] = """\b(true|false)\b""".r 

Ich habe zur Zeit etwa 70 Tests, die alle jetzt vorbei sind. Ich bin immer noch offen für Vorschläge für robustere Lösungen (weniger spröde), aber das scheint jetzt gut genug zu funktionieren.