For more technical information about the JEXL Grammar, you can find the
JavaCC
grammar for JEXL
here:
Parser.jjt
Language Elements
Specified using
##
or
//
and extend to the end of line, e.g.
## This is a comment
Also specified using
//
, e.g.
// This is a comment
Multiple lines comments are specified using
/*...*/
, e.g.
/* This is a
multi-line comment */
NOTE:
JEXL does not support variables with hyphens in them, e.g.
commons-logging // invalid variable name (hyphenated)
is not a valid variable, but instead is treated as a
subtraction of the variable
logging
from the variable
commons
JEXL also supports
ant-style
variables, the following is a valid variable name:
my.dotted.var
N.B.
the following keywords are reserved, and cannot be used as a variable name or property when using the dot operator:
or and eq ne lt gt le ge div mod not null true false new var do while break continue function return
For example, the following is invalid:
my.new.dotted.var // invalid ('new' is keyword)
In such cases, quoted identifiers or the [ ] operator can be used, for example:
my.'new'.dotted.var
my['new'].dotted.var
A script in JEXL is made up of zero or more statements, optionally enclosed in a function definition block.
Scripts can include one or more pragmas.
Scripts can be read from a String, File or URL.
Scripts can be created with named parameters which allow a later evaluation to be performed with arguments.
By default, in the absence of an explicit
return
statement, scripts return the value of the
last evaluated statement.
Using the
return
keyword, a script will return the expression that follows (or null).
Declares a pragma, a method to communicate information from a script to its execution environment, e.g.
#pragma execution.option 42
will declare a pragma named
execution.option
with
a value of
42
.
Pragma keys can be identifiers or antish names, pragma values can be literals (boolean, integer,
real, string, null, NaN) and antish names
Although pragmas are statements, they are
not
evaluated at runtime; they are constants
associated to the script after parsing. It is expected that user code accesses the pragma map from
scripts to alter some functions behavior.
There are 3 built-in pragmas:
jexl.options
overrides the evaluation options using the option-flags syntax.
#pragma jexl.options "+strict -safe +lexical +lexicalShade"
will let the script run with
the options strict being true, safe false, lexical true and lexical shade true.
jexl.namespace.
ns_prefix
declares a namespace valid for the script
execution lifetime, the value being the fully-qualified namespace class-name.
#pragma jexl.namespace.str java.lang.String
declare 'str' as the namespace using
the String class.
jexl.import
works in conjunction with the 'new' operator.
It declares the packages that a
new ClassIdentifier(...)
will visit - in
declaration order - to resolve the class name into a fully-qualified class-name.
#pragma jexl.import java.net
will allow executing
let url = new URL("https://commons.apache.org");
,
the URL class name being solved as the java.net.URL fully-qualified class-name.
A statement can be the empty statement, the semicolon (
;
), block, conditional, variable declaration or an expression.
Statements are optionally terminated with a semicolon.
A single statement or a statement block can be annotated.
let
declares a local variable (or a parameter) with a lexical block scope. The
variable can only be accessed within its definition block and any nested sub-block. This also
forbids variable redeclaration within that scope. Note: This emulates Java behavior which differs
from ECMAScript.
const
behaves as
let
but will prevent the variable from being
reassigned by any side effect operator.
var
declares a variable whose scope is the whole script and allows redefinition.
This behavior is altered by the
JexlFeature#setLexical(true)
that will enforce a
lexical scope for all variables akin to
let
declaration.
Invalid declaration:
var x.y;
Local variables they take precedence in resolution over contextual variables.
When scripts are created with named parameters, those behave as local variables.
Local variables can not use
ant-style
naming, only one identifier.
An expression can be the literal, variable, assignment, access operator, function definition, function call, method call or
an evaluation operator.
Assigns the value of a variable (
my.var = 'a value'
) using a
JexlContext
as initial resolver. Both
beans
and
ant-ish
variables assignment are supported.
Defines a function within the script, usually associated with a local variable assignment.
var fun = function(x, y) { x + y }
The following syntaxes are also supported:
var fun = (x, y) -> { x + y }
var fun = (let x, let y) -> { x + y }
const fun = (const x, const y) -> { x + y }
function fun(const x, const y) { x + y }
If the function has only one argument the parentheses may be omitted
var fun = x -> { x * x }
.
Functions with an expression as body can forego the curly brackets as in:
var fun = x -> x * x
.
Note that functions can use local variables and parameters from their declaring script.
Those variables values are bound to the function environment at definition time.
var t = 20; var s = function(x, y) {x + y + t}; t = 54; s(15, 7)
The function closure captures 't' when defined; the result of the evaluation will
lead to
15 + 7 + 20 = 42
.
Calling a function follows the usual convention, e.g.
fun(17, 25)
will call the
fun
function with arguments
17
and
25
Calls a method of an object, e.g.
"hello world".hashCode()
will call the
hashCode
method
of the
"hello world"
String.
In case of multiple arguments and overloading, JEXL will make the best effort to find
the most appropriate non ambiguous method to call.
Allows to evaluate a property of an object, a value of the collection or an array
by using either square brackets or a dotted numeral, e.g.
foo.bar
will access the
bar
property
of the
foo
Object.
arr1[0]
will access the first element of the
arr1
array.
The safe-access operator
foo?.bar
shortcuts any null or non-existent references
along the navigation path, allowing a safe-navigation free of errors. In the previous expression,
if 'foo' is null, the whole expression will evaluate as null. This is an efficient shortcut
to defensive expressions such as
x?.y?.z
that would otherwise be expressed as
x? (x.y? (x.y.z ?: null) :null) : null
.
Properties can also be quoted as in
foo.'b a r'
and can be dynamic
back-quoted interpolation strings as in
cal.`${dd.year}-${dd.month}-${dd.day}`
.
These syntaxes are mixable with safe-access as in
foo.'b a r'?.quux
or
foo?.`${bar}`.quux
.
The safe-access array operator (as in
foo?[bar]
) provides the same behavior as
the safe-access operator and shortcuts any null or non-existent references
along the navigation path, allowing a safe-navigation free of errors.
In the previous expression, if 'foo' is null, the whole expression will evaluate as null.
Note that this can also be used in a chain as in
x?[y]?[z]
.
Access operators can be overloaded in
JexlArithmetic
, so that
the operator behavior will differ depending on the type of the operator arguments
Performs computational, logical or comparative action between one, two or three arguments
whose values are expressions, e.g.
40 + 2
will call the
add
operator
between two integer literals.
All operators, except when stated otherwise, can be overloaded in
JexlArithmetic
, so that the action taken
will differ depending on the type of the operator arguments
Annotations in JEXL are 'meta-statements'; they allow to wrap the execution of the JEXL statement in a user provided
caller; typical example would be:
@synchronized(x) x.someMethod();
Annotations may be declared with zero or more parameters;
@lenient x.someMethod();
@synchronized(x) x.someMethod();
@parallel(pool, 8) x.someMethod();
They also can be chained as in:
@lenient @silent x.someMethod();
Annotation processing is implemented by providing a JexlContext.AnnotationProcessor; its processAnnotation
method will call the annotated statement encapsulated in a Callable. Annotation arguments are evaluated
and passed as arguments to processAnnotation.
1 or more digits from
0
to
9
, followed
by a decimal point and then one or more digits from
0
to
9
,
optionally followed by
f
or
F
,
eg
42.0
or
42.0f
.
1 or more digits from
0
to
9
, followed
by a decimal point and then one or more digits from
0
to
9
suffixed with
d
or
D
, eg
42.0d
.
A special literal
NaN
can be used to denote
Double.NaN
constant
1 or more digits from
0
to
9
, followed
by a decimal point and then one or more digits from
0
to
9
suffixed with
b
or
B
)
, eg
42.0b
.
Natural numbers (i.e. Integer, Long, BigInteger) can also be expressed as octal or hexadecimal using the same format as Java.
i.e. prefix the number with
0
for octal, and prefix with
0x
or
0X
for hexadecimal.
For example
010
or
0x10
.
Real numbers (i.e. Float, Double, BigDecimal) can also be expressed using standard Java exponent notation.
i.e. suffix the number with
e
or
E
followed by the sign
+
or
-
followed by one or more decimal digits.
For example
42.0E-1D
or
42.0E+3B
.
Can start and end with either
'
or
"
delimiters, e.g.
"Hello world"
and
'Hello world'
are equivalent.
The escape character is
\
(backslash).
Unicode characters can be used in string literals;
Unicode escape sequences consist of:
-
a backslash '\'
-
a 'u'
-
4 hexadecimal digits ([0-9],[A-H],[a-h]).
Such sequences represent the UTF-16 encoding of a Unicode character,
for example,
'a'
is equivalent to
'\u0061'
.
Start and end with
`
delimiter - back-quote -, e.g.
`Hello world`
The escape character is
\
(backslash); Unicode escape sequences can also be used.
These format literals can span multiple lines and allow Unified JEXL expressions (JSTL like expressions)
to be interpolated. If a variable
user
valued
JEXL
is present in the environment - whether
as a local or global variable -, the format
`Hello ${user}`
will evaluate as
Hello JEXL
.
A
[
followed by zero or more expressions separated by
,
and ending
with
]
, e.g.
[ 1, 2, "three" ]
This syntax creates an
Object[]
.
Empty array literal can be specified as
[]
with result of creating
Object[]
JEXL will attempt to strongly type the array; if all entries are of the same class or if all
entries are Number instance, the array literal will be an
MyClass[]
in the former
case, a
Number[]
in the latter case.
Furthermore, if all entries in the array literal are of the same class
and that class has an equivalent primitive type, the array returned will be a primitive array. e.g.
[1, 2, 3]
will be interpreted as
int[]
.
A
[
followed by zero or more expressions separated by
,
and ending
with
,...]
, e.g.
[ 1, 2, "three",...]
This syntax creates an
ArrayList<Object>
.
Empty list literal can be specified as
[...]
A
{
followed by zero or more expressions separated by
,
and ending
with
}
, e.g.
{ "one" , 2, "more"}
This syntax creates a
HashSet<Object>
.
Empty set literal can be specified as
{}
A
{
followed by zero or more sets of
key : value
pairs separated by
,
and ending
with
}
, e.g.
{ "one" : 1, "two" : 2, "three" : 3, "more": "many more" }
This syntax creates a
HashMap<Object,Object>
.
Empty map literal can be specified as
{:}
1 .. 42
This syntax creates a 'range' object in the form of a java iterable which can be used in for statement, e.g.
for (i : 1..42) a = a + b[i]
-
An instance of class C and the derived JexlArithmetic overloads a method 'public boolean empty(C arg)'
that returns true when the argument is considered empty
-
An empty string
-
An array of length zero
-
A collection of size zero
-
An empty map
-
Defining a method 'public boolean isEmpty()'
that returns true when the instance is considered empty
This is false in other cases (besides errors).
empty(arg)
-
0 if the argument is null
-
The result of calling a method from a derived JexlArithmetic overload 'public int size(C arg)',
C being the class of the argument
-
Length of an array
-
Length of a string
-
Size of a Collection
-
Size of a Map
-
The result of calling a method 'public int size()' defined by the argument class
This returns 0 in other cases (besides errors).
size("Hello")
returns 5.
Creates a new instance using a fully-qualified class name or Class:
new("java.lang.Double", 10)
returns 10.0.
Note that the first argument of
new
can be a variable or any
expression evaluating as a String or Class; the rest of the arguments are used
as arguments to the constructor for the class considered.
In case of multiple constructors, JEXL will make the best effort to find
the most appropriate non ambiguous constructor to call.
Alternatively, using
#pragma jexl.import java.lang
code>, one can use the
following syntax:
new Double(10)
.
Top level function is a function which can be invoked without specifying a namespace.
Top level function can be defined by the function definition method inside the script
A
JexlContext
can define methods which can be invoked as top level functions.
This can allow expressions like:
string(23.0)
Another way to define top level function is to register to
JexlEngine
objects or classes
with null namespace.
A
JexlEngine
can register objects or classes used as function namespaces.
This can allow expressions like:
math:cosinus(23.0)
The usual
&&
operator can be used as well as the word
and
, e.g.
cond1 and cond2
and
cond1 && cond2
are equivalent.
Note that this operator can not be overloaded
The usual
||
operator can be used as well as the word
or
, e.g.
cond1 or cond2
and
cond1 || cond2
are equivalent.
Note that this operator can not be overloaded
The usual
!
operator can be used as well as the word
not
, e.g.
!cond1
and
not cond1
are equivalent.
Note that this operator can not be overloaded
The left shift operator (<<) shifts the first operand the specified number of bits to the left.
1 << 2
= 4
The right shift operator (>>) shifts the first operand the specified number of bits to the right.
4 >> 2
= 1
(zero-fill right shift) shifts the first operand the specified number of bits to the right.
The sign bit becomes 0, so the result is always non-negative.
The usual ternary conditional operator
condition ? if_true : if_false
operator can be
used as well as the abbreviation
value ?: if_false
which returns the
value
if
its evaluation is defined, non-null and non-false, e.g.
val1 ? val1 : val2
and
val1 ?: val2
are equivalent.
NOTE:
The condition will evaluate to
false
when it
refers to an undefined variable or
null
for all
JexlEngine
flag combinations. This allows explicit syntactic leniency and treats the condition
'if undefined or null or false' the same way in all cases.
Note that this operator can not be overloaded
The null coalescing operator returns the result of its first operand if it is defined and is not null.
When
x
and
y
are null or undefined,
x ?? 'unknown or null x'
evaluates as
'unknown or null x'
y ?? "default"
evaluates as
"default"
.
When
var x = 42
and
var y = "forty-two"
,
x??"other"
evaluates as
42
and
y??"other"
evaluates as
"forty-two"
.
NOTE:
this operator does not behave like the ternary conditional since it
does not coerce the first argument to a boolean to evaluate the condition.
When
var x = false
and
var y = 0
,
x??true
evaluates as
false
and
y??1
evaluates as
0
.
Note that this operator can not be overloaded
The equality
==
operator checks whether its two operands are equal, returning a
Boolean result. Unlike the strict equality operator, it attempts to convert and compare
operands that are of different types.
null
is only ever equal to null, that is if you compare null
to any non-null value, the result is false.
-
Equality uses the java
equals
method
The inequality
!=
operator checks whether its two operands are not equal,
returning a Boolean result. Unlike the strict inequality operator, it attempts to convert and
compare operands that are of different types.
Strict-Equality
The strict equality
===
operator checks whether its two operands are equal,
returning a Boolean result. Unlike the equality operator, the strict equality operator always
considers operands of different types to be different.
The strict inequality
!==
operator checks whether its two operands are not equal,
returning a Boolean result. Unlike the inequality operator, the strict inequality operator
always considers operands of different types to be different
The usual
<=
operator can be used as well as the abbreviation
le
.
For example
val1 <= val2
and
val1 le val2
are equivalent.
The usual
>=
operator can be used as well as the abbreviation
ge
.
For example
val1 >= val2
and
val1 ge val2
are equivalent.
The syntactically Perl inspired
=~
operator can be used to check that a
string
matches
a regular expression (expressed either a Java String or a java.util.regex.Pattern).
For example
"abcdef" =~ "abc.*
returns
true
.
It also checks whether any collection, set or map (on keys) contains a value or not; in that case, it behaves
as an "in" operator.
Note that arrays and user classes exposing a public 'contains' method will allow their instances
to behave as right-hand side operands of this operator.
"a" =~ ["a","b","c","d","e",f"]
returns
true
.
The syntactically Perl inspired
!~
operator can be used to check that a
string
does not
match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
For example
"abcdef" !~ "abc.*
returns
false
.
It also checks whether any collection, set or map (on keys) does not contain a value; in that case, it behaves
as "not in" operator.
Note that arrays and user classes exposing a public 'contains' method will allow their instances
to behave as right-hand side operands of this operator.
"a" !~ ["a","b","c","d","e",f"]
returns
false
.
The
=^
operator is a short-hand for the 'startsWith' method.
For example,
"abcdef" =^ "abc"
returns
true
.
Note that through duck-typing, user classes exposing a public 'startsWith' method will allow their instances
to behave as left-hand side operands of this operator.
The
=$
operator is a short-hand for the 'endsWith' method.
For example,
"abcdef" =$ "def"
returns
true
.
Note that through duck-typing, user classes exposing an 'endsWith' method will allow their instances
to behave as left-hand side operands of this operator.
Some operators exist in side-effect forms.
Their default behavior is to execute the operator and assign the left-hand side with the result.
For instance
a += 2
is equivalent to
a = a + 2
The list of operators is:
The unary
+
operator is used. It performs an integral promotion meaning
that byte, short, char arguments will be promoted to integer as a result.
For example
+12
or
+(a * b)
instanceof/!instanceof
The instanceof operator allows to check whether an object belongs to a certain class.
It is using Class.isInstance to perform the check. As a convenience, {{ !instanceof }} is
supported as well.
Map elements are accessed using square brackets, e.g.
map[0]; map['name']; map[var];
Note that
map['7']
and
map[7]
refer to different elements.
Map elements with a numeric key may also be accessed using a dotted numeral, e.g.
map[0]
and
map.0
are equivalent.
Note that
map.1
and
map.01
refer to different elements,
while
map.1
and
map[01]
are equivalent.
Properties of JavaBean objects that define appropriate getter methods can be accessed
using either square brackets or a dotted numeral, e.g.
foo['bar']
and
foo.bar
are equivalent.
The appropriate
Foo.getBar()
method will be called.
Note that both
foo.Bar
and
foo.bar
can be used
Indexed properties of JavaBean objects that define appropriate getter methods can be accessed
using either square brackets or a dotted numeral, e.g.
x.attribute['name']
and
x.attribute.name
are equivalent.
The appropriate
Foo.getAttribute(String index)
method will be called
Public fields of java objects can be accessed using either square brackets or a dotted numeral, e.g.
foo['bar']
and
foo.bar
are equivalent.
Properties of Java classes that define public
Object get(String name)
method can be accessed
using either square brackets or a dotted numeral, e.g.
foo['bar']
and
foo.bar
are equivalent.
The appropriate
Foo.get(String index)
method will be called with the argument of
"bar"
String
Loop through items of an Array, Collection, Map, Iterator or Enumeration, e.g.
for (let item : list) {
x = x + item;
Where
list
is a context variable pointing to any iterable structure.
The following syntax using a context variable is also supported:
for (item : list) {
x = x + item;
Note that the loop variable
item
is accessible after loop evaluation
Finally, the conventional syntax using a local variable, initial value, loop condition and
loop step is supported.
for (let i = 0; i < size(list); ++i) {
x = x + list[i];
Where
list
is a local variable pointing to an array-like structure.
The JEXL 1.1 syntax using
foreach(item in list)
is now
unsupported
.
Within loops (do/while/for), terminates execution of the statements in the current iteration
and skips to the next loop iteration.
let text = '';
for (let i : (4..2)) {
if (i === 3) {
continue;
text += i;
text;
Will evaluate as "42".
Within loops (do/while/for), terminates execution of the statements in the current iteration
and exits the loop.
let i = 33;
while (i < 66) {
if (i == 42) {
break;
i += 1;
Will evaluate as 42.
The return statement ends execution and specifies a value to be returned to the script
or lambda caller.
function f(x) {
if (x == 42) {
return "The answer to life, the universe, and everything";
return x
f(41);
Will evaluate as 41.
The throw statement throws an object. Execution of the current script will stop
(the statements after throw won't be executed), and control will be passed to the first catch
block in the call stack. If no catch block exists among caller functions, script execution will
terminate.
try/catch/finally
The try...catch statement is comprised of a try block and either a catch block, a finally block,
or both. The code in the try block is executed first, and if it throws an exception, the code in
the catch block will be executed.
The code in the finally block will always be executed before control flow exits the entire construct.
try {
return 42/0;
} catch (let e) {
// handle the error
The following syntax is also supported:
try {
return 42/0;
} catch (var e) {
// handle the error
try-with-resources
The try-with-resources statement is a try statement that declares one or more resources.
The try-with-resources statement ensures that each resource is closed at
the end of the statement; any object that exposes a
close()
method will see that
method invoked.
let g = open("g");
try(let f = open("f"), g) {
// more code