2016-04-08 2 views
3

Quand je lance ce test:Scala Combinators JavaTokennParsers '||' attendu, mais « > » trouvé

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

Je reçois:

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

Si je change l'expression de ((1+2) > (3+4)) il fonctionne, mais je dois être en mesure de soutenir l'option parenthèses. Aucune suggestion? Voici la définition de classe, ainsi que la trace du journal:

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

La trace du journal:

trying root at [email protected]004 
trying expr at [email protected]004 
trying bool_expr at [email protected]004 
trying or at scala.util.parsing.combinator.PackratParsers$[email protected] 
trying and at [email protected]004 
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 sca[email protected]59d016c9 
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

Notez que '(1 + 2)' est analysé avec 'bool_expr':' bool_parens -> [1.6] analysé: BinaryOp (+, Number (1.0), Number (2.0), Num) '. Mais il devrait être analysé avec un 'num_expr' pour l'analyseur' relationnel 'requis à considérer. – Kolmar

+0

Aussi, je crois que certains types de 'foldExpr' sont faux. Par exemple. dans 'num_equality' le résultat devrait être' ExprType.Bool', mais il a '^^ foldExpr (ExprType.Num)'. – Kolmar

+0

@Kolmar J'ai pu le faire fonctionner en inversant l'ordre des parseurs d'égalité de sorte que 'num_equality' vienne avant' bool_equality'. Cependant, quand je l'ai fait, 'true' et' false' ont été analysés en tant que valeurs 'ident' au lieu de mots-clés booléens réservés. J'ai donc ajouté un analyseur 'reserved' pour corriger cela. Je crois que cela correspond à votre recommandation. –

Répondre

0

@Kolmar est exact que l'entrée doit être analysée avec num_expr. La solution ci-dessous est ce que je suis venu avec, mais il se sent moins robuste que je le voudrais. Le problème se résume à la précédence mais j'espérais qu'il y avait un moyen d'obtenir que l'analyseur continue à vérifier après l'échec de la branche booléenne. Au lieu de cela, j'ai troqué l'ordre de l'analyseur equality pour vérifier num_equality avant bool_equality:

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

Mais parce que true et false mots-clés sont définis dans la branche booléenne, qui est évaluée après des valeurs numériques, ils ont été analysés en tant que type AnyVar au lieu de Bool. Pour résoudre ce problème, j'ai ajouté un analyseur reserved et changé le var_factor à ce qui suit:

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 

J'ai actuellement environ 70 tests qui sont tous aujourd'hui pris. Je suis toujours ouvert aux suggestions pour des solutions plus robustes (moins cassantes) mais cela semble fonctionner assez bien pour le moment.