Use literal operators (eg “and”, “or”) in Groovy expressions?

别来无恙 提交于 2019-12-21 05:09:13

问题


My current work project allows user-provided expressions to be evaluated in specific contexts, as a way for them to extend and influence the workflow. These expressions the usual logical ones f. To make it a bit palatable for non-programmers, I'd like to give them the option of using literal operators (e.g. and, or, not instead of &, |, !).

A simple search & replace is not sufficient, as the data might contains those words within quotes and building a parser, while doable, may not be the most elegant and efficient solution.

To make the question clear: is there a way in Groovy to allow the users to write

x > 10 and y = 20 or not z 

but have Groovy evaluate it as if it were:

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

Thank you.


回答1:


Recent versions of Groovy support Command chains, so it's indeed possible to write this:

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

The word "compute" here is arbitrary, but it cannot be omitted, because it's the first "verb" in the command chain. Everything that follows alternates between verb and noun:

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

A command chain is compiled like this:

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

so the example above is compiled to:

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

There are many ways to implement this. Here is just a quick & dirty proof of concept, that doesn't implement operator precedence, among other things:

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 }

You can use command chains by themselves (as top-level statements) or to the right-hand side of an assignment operator (local variable or property assignment), but not inside other expressions.




回答2:


If you can swap operators like > and = for the facelets-like gt and eq, respectively, i THINK your case may be doable, though it will require a lot of effort:

x gt 10 and y eq 20 or not z 

resolves to:

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

And this will be hell to parse.

The way @Brian Henry suggested is the easiest way, though not user-friendly, since it needs the parens and dots.

Well, considering we can swap the operators, you could try to intercept the Integer.call to start expressions. Having the missing properties in a script being resolved to operations can solve your new keywords problem. Then you can build expressions and save them to a list, executing them in the end of the script. It's not finished, but i came along with this:

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

Which will give an exception, due the not() part not being implemented, but it can build two Instruction objects before failing:

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

Not sure if it is worth it.




回答3:


The GDK tacks on and() and or() methods to Boolean. If you supplied a method like

Boolean not(Boolean b) {return !b}

you could write something like

(x > 10).and(y == 20).or(not(4 == 1)) 

I'm not sure that's particularly easy to write, though.



来源:https://stackoverflow.com/questions/13778737/use-literal-operators-eg-and-or-in-groovy-expressions

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!