如何分析与斯卡拉过滤条件的字符串,并用它来过滤对象(How to parse a string wi

2019-10-19 07:46发布

比方说,我有对象的数组,其中包含一些字符串,整数和枚举值。 而且还含有这些类型和方法返回这些类型的数组。

例如包含数组以下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}  }  

从命令行我通过在与过滤器标准来阶应用的字符串。 例如:

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

运营商应该做你在一个正常的期望是什么,如果这些类型的值的声明。

如何可以解析过滤条件字符串,并用它来从一个数组过滤ExampleObjects?

或者我应该在哪里开始阅读,找出如何做到这一点?

Answer 1:

如果你想限制输入到有限的语言,你可以轻松地创建仅使用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一个布尔值的函数。 在解析输入字符串,利用预先定义的辅助函数,并在解析器规则中定义的匿名函数此测试功能被构造一次。 输入字符串的解释只进行一次,而构建的测试功能。 当您执行的测试功能,您将运行编译的代码规模。 所以它应该运行相当快。

该测试功能是安全的,因为它不允许用户运行任意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使用更多的功能或多个字段。



Answer 2:

你可能想看看Twitter的评估和演示工具库,你可以在这里找到在GitHub上 。 你可以只替换在过滤逻辑放到你想使用它,并把它传递给eval函数点的String传递。



文章来源: How to parse a string with filter criteria in scala and use it to filter objects