如何使用scala中的过滤条件解析字符串并使用它来过滤对象

时间:2014-02-05 15:33:40

标签: scala parsing filter

假设我有一个包含一些String,Integer和Enum值的对象数组。并且还包含返回这些类型的这些类型和方法的数组。

例如,包含以下ExampleObject的数组:

object WeekDay extends Enumeration { type WeekDay = Value; val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value  }

class ExampleObject (val integerValue1 : Integer, val integerValue2 : Integer, val stringValue1 : String, val weekDay: WeekDay.Value, val integerArray : Array[Integer])
{  def intReturningMethod1()= {0}  }  

从命令行我将带有过滤条件的字符串传递给scala应用程序。例如:

-filter_criteria "((integerValue1 > 100 || integerValue2 < 50) && (stringValue1 == "A" || weekDay != "Mon")) || (integerArray(15) == 1) "

运算符应该在具有这些类型值的普通if语句中执行您期望的操作。

如何解析过滤条件字符串并使用它来过滤数组中的ExampleObjects?

或者我应该从哪里开始阅读以了解如何执行此操作?

2 个答案:

答案 0 :(得分:3)

如果要将输入限制为有限的语言,只需使用Scala核心库就可以轻松地为该语言创建解析器。

我已经为你的例子

的精简版做了这个
  object WeekDay extends Enumeration {
    type WeekDay = Value; val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
  }

  case class ExampleObject(val integerValue1 : Integer, val stringValue1 : String, val weekDay: WeekDay.Value){
    def intReturningMethod1()= {0}
  }

首先我使用导入并创建一些助手:

  type FilterCriterion = ExampleObject => Boolean
  type Extractor[T] = ExampleObject => T

  def compare[T <% Ordered[T]](v1 : T, c : String, v2 : T) : Boolean = c match {
    case "<" => v1 < v2
    case ">" => v1 > v2
    case "==" => v1 == v2
  }

  def compareAny(v1: Any, c : String, v2 : Any) : Boolean = (v1,v2) match {
    case (s1: String, s2:String) => compare(s1,c,s2)
    case (i1: Int, i2 : Int) => compare(i1,c,i2)
    case (w1 : WeekDay.WeekDay, w2 : WeekDay.WeekDay) => compare(w1.id, c, w2.id)
    case _ => throw new IllegalArgumentException(s"Cannot compare ${v1.getClass} with ${v2.getClass}")
  }

然后我创建解析器:

  object FilterParser extends JavaTokenParsers {
    def intExtractor : Parser[Extractor[Int]] = wholeNumber ^^ {s => Function.const(s.toInt)_} |
      "intReturningMethod1()" ^^^ {(e : ExampleObject) => e.intReturningMethod1()}  |
      "integerValue1" ^^^ {_.integerValue1}
    def stringExtractor : Parser[Extractor[String]] = stringLiteral ^^ {s => Function.const(s.drop(1).dropRight(1))_} |
      "stringValue1" ^^^ {_.stringValue1}
    def weekDayExtrator : Parser[Extractor[WeekDay.WeekDay]] = stringLiteral ^? {
      case s if WeekDay.values.exists(_.toString == s) => Function.const(WeekDay.withName(s))_
    }
    def extractor : Parser[Extractor[Any]] = intExtractor | stringExtractor | weekDayExtrator

    def compareOp : Parser[FilterCriterion] = (extractor ~ ("<"| "==" | ">") ~ extractor) ^^ {
      case v1 ~ c ~ v2 => (e : ExampleObject) => compareAny(v1(e),c,v2(e))
    }

    def simpleExpression : Parser[FilterCriterion] = "(" ~> expression <~ ")" | compareOp
    def notExpression : Parser[FilterCriterion] = "!" ~> simpleExpression ^^ {(ex) => (e : ExampleObject) => !ex(e)} |
      simpleExpression
    def andExpression : Parser[FilterCriterion] = repsep(notExpression,"&&") ^^ {(exs) => (e : ExampleObject) => exs.foldLeft(true)((b,ex)=> b && ex(e))}
    def orExpression : Parser[FilterCriterion] = repsep(andExpression,"||") ^^ {(exs) => (e : ExampleObject) => exs.foldLeft(false)((b,ex)=> b || ex(e))}
    def expression : Parser[FilterCriterion] = orExpression

    def parseExpressionString(s : String) = parseAll(expression, s)
  }

此解析器获取您的输入字符串并返回一个将ExampleObject映射到布尔值的函数。使用预定义的辅助函数和解析器规则中定义的匿名函数,在解析输入字符串时构造此测试函数一次。在构造测试函数时,输入字符串的解释只进行一次。执行测试功能时,将运行已编译的Scale代码。所以它应该运行得非常快。

测试功能是安全的,因为它不允许用户运行任意Scala代码。它只是由解析器中提供的部分函数和预定义的帮助器构建。

  val parsedCriterion=FilterParser.parseExpressionString("""((integerValue1 > 100 || integerValue1 < 50) && (stringValue1 == "A"))""")

  List(ExampleObject(1,"A", WeekDay.Mon), ExampleObject(2,"B", WeekDay.Sat), ExampleObject(50,"A", WeekDay.Mon)).filter(parsedCriterion.get)

当您希望它在ExampleObject中使用更多函数或更多字段时,您可以自己扩展解析器。

答案 1 :(得分:1)

您可能想看看Twitter的Eval实用程序库,您可以find here on GitHub。您可以将传入的过滤逻辑替换为您想要使用它的点,并将其传递给eval函数。