在Groovy表达式中使用文字运算符(例如“and”,“或”)?

时间:2012-12-08 15:06:36

标签: groovy dsl

我当前的工作项目允许在特定的上下文中评估用户提供的表达式,作为他们扩展和影响工作流的一种方式。这些表达式通常是逻辑上的。为了使它对非程序员来说有点可口,我想给他们选择使用文字运算符(例如和,或者,而不是代替&,|,!)。

简单搜索&替换是不够的,因为数据可能包含引号内的那些单词并构建解析器,虽然可行,但可能不是最优雅和有效的解决方案。

要明确问题:Groovy中是否有一种方法允许用户编写

x > 10 and y = 20 or not z 

但是让Groovy对它进行评估,就像它一样:

x > 10 && y == 20 || !z

谢谢。

3 个答案:

答案 0 :(得分:4)

最新版本的Groovy支持Command chains,因此确实可以写这个:

compute x > 10 and y == 20 or not(z)

单词" compute"这是任意的,但不能省略,因为它是第一个"动词"在命令链中。随后的所有内容在动词和名词之间交替出现:

 compute  x > 10  and  y == 20  or   not(z)
 ───┬───  ──┬───  ─┬─  ───┬───  ─┬─  ──┬───
   verb    noun   verb   noun   verb  noun

命令链的编译如下:

verb(noun).verb(noun).verb(noun)...

所以上面的例子编译为:

compute(x > 10).and(y == 20).or(not(z))

有很多方法可以实现这一点。这只是一个快速的&脏概念,不会实现运算符优先级,其中包括:

class Compute {
    private value
    Compute(boolean v) { value = v }
    def or (boolean w) { value = value || w; this }
    def and(boolean w) { value = value && w; this }
    String  toString() { value }
}

def compute(v) { new Compute(v) }
def not(boolean v) { !v }

您可以单独使用命令链(作为顶级语句)或使用赋值运算符的右侧(局部变量或属性赋值),但不能在其他表达式中使用。

答案 1 :(得分:1)

如果您可以分别将>=之类的运算符换成类似小小的gteq,我认为您的情况可能是可行的,尽管它会需要付出很多努力:

x gt 10 and y eq 20 or not z 

解析为:

x(gt).10(and).y(eq).20(or).not(z)

这将是解析的地狱。

@Brian Henry提出的方式是最简单的方法,虽然不是用户友好的,因为它需要parens和dot。

好吧,考虑到我们可以交换运算符,你可以尝试拦截Integer.call来启动表达式。将脚本中缺少的属性解析为操作可以解决新的关键字问题。然后,您可以构建表达式并将它们保存到列表中,并在脚本的末尾执行它们。它还没有完成,但我带来了这个:

// the operators that can be used in the script
enum Operation { eq, and, gt, not }

// every unresolved variable here will try to be resolved as an Operation
def propertyMissing(String property) { Operation.find { it.name() == property} }

// a class to contain what should be executed in the end of the script
@groovy.transform.ToString 
class Instruction { def left; Operation operation; def right }

// a class to handle the next allowed tokens
class Expression {
  Closure handler; Instruction instruction
  def methodMissing(String method, args) {
    println "method=$method, args=$args"
    handler method, args
  }
}

// a list to contain the instructions that will need to be parsed
def instructions = []

// the start of the whole mess: an integer will get this called
Integer.metaClass {
  call = { Operation op ->
    instruction = new Instruction(operation: op, left: delegate)
    instructions << instruction
    new Expression(
      instruction: instruction,
      handler:{ String method, args -> 
        instruction.right = method.toInteger()
        println instructions
        this
      })
  }
}

x = 12
y = 19
z = false

x gt 10 and y eq 20 or not z 

由于not()部分没有实现,会产生异常,但它可以在失败之前构建两个Instruction对象:

[Instruction(12, gt, 10), Instruction(19, eq, 20)]

不确定是否值得。

答案 2 :(得分:0)

and()or()Boolean not(Boolean b) {return !b} 方法解决为布尔值。如果您提供了类似

的方法
(x > 10).and(y == 20).or(not(4 == 1)) 
你可以写点像

{{1}}

但我不确定这写起来特别容易。