wishihadabettername
wishihadabettername

Reputation: 14751

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

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.

Upvotes: 5

Views: 52233

Answers (3)

Tobia
Tobia

Reputation: 18811

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.

Upvotes: 4

Will
Will

Reputation: 14529

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.

Upvotes: 1

Brian Henry
Brian Henry

Reputation: 3171

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.

Upvotes: 0

Related Questions