245 lines
8.2 KiB
Plaintext
245 lines
8.2 KiB
Plaintext
[[modules-scripting-painless-syntax]]
|
|
=== Painless Syntax
|
|
|
|
experimental[The Painless scripting language is new and is still marked as experimental. The syntax or API may be changed in the future in non-backwards compatible ways if required.]
|
|
|
|
[float]
|
|
[[painless-types]]
|
|
=== Variable types
|
|
|
|
Painless supports all of https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html[Java's types],
|
|
including array types, but adds some additional built-in types.
|
|
|
|
[float]
|
|
[[painless-def]]
|
|
==== Def
|
|
|
|
The dynamic type `def` serves as a placeholder for any other type. It adopts the behavior
|
|
of whatever runtime type it represents.
|
|
|
|
[float]
|
|
[[painless-strings]]
|
|
==== String
|
|
|
|
String constants can be declared with single quotes, to avoid escaping horrors with JSON:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
def mystring = 'foo';
|
|
---------------------------------------------------------
|
|
|
|
[float]
|
|
[[painless-arrays]]
|
|
==== Arrays
|
|
|
|
Arrays can be subscripted starting from `0` for traditional array access or with
|
|
negative numbers to starting from the back of the array. So the following
|
|
returns `2`.
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
int[] x = new int[5];
|
|
x[0]++;
|
|
x[-5]++;
|
|
return x[0];
|
|
---------------------------------------------------------
|
|
|
|
|
|
[float]
|
|
[[painless-lists]]
|
|
==== List
|
|
|
|
Lists can be created explicitly (e.g. `new ArrayList()`) or initialized similar to Groovy:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
def list = [1,2,3];
|
|
---------------------------------------------------------
|
|
|
|
Lists can also be accessed similar to arrays. They support `.length` and
|
|
subscripts, including negative subscripts to read from the back of the list:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
def list = [1,2,3];
|
|
list[-1] = 5
|
|
return list[0]
|
|
---------------------------------------------------------
|
|
|
|
[float]
|
|
[[painless-maps]]
|
|
==== Map
|
|
|
|
Maps can be created explicitly (e.g. `new HashMap()`) or initialized similar to Groovy:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
def person = ['name': 'Joe', 'age': 63];
|
|
---------------------------------------------------------
|
|
|
|
Map keys can also be accessed as properties.
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
def person = ['name': 'Joe', 'age': 63];
|
|
person.retired = true;
|
|
return person.name
|
|
---------------------------------------------------------
|
|
|
|
Map keys can also be accessed via subscript (for keys containing special characters):
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
return map['something-absurd!']
|
|
---------------------------------------------------------
|
|
|
|
[float]
|
|
[[painless-pattern]]
|
|
==== Pattern
|
|
|
|
Regular expression constants are directly supported:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
Pattern p = /[aeiou]/
|
|
---------------------------------------------------------
|
|
|
|
Patterns can only be created via this mechanism. This ensures fast performance, regular expressions
|
|
are always constants and compiled efficiently a single time.
|
|
|
|
[float]
|
|
[[modules-scripting-painless-regex-flags]]
|
|
==== Pattern flags
|
|
|
|
You can define flags on patterns in Painless by adding characters after the
|
|
trailing `/` like `/foo/i` or `/foo \w #comment/iUx`. Painless exposes all the
|
|
flags from
|
|
https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html[Java's Pattern class]
|
|
using these characters:
|
|
|
|
[cols="<,<,<",options="header",]
|
|
|=======================================================================
|
|
| Character | Java Constant | Example
|
|
|`c` | CANON_EQ | `'å' ==~ /å/c` (open in hex editor to see)
|
|
|`i` | CASE_INSENSITIVE | `'A' ==~ /a/i`
|
|
|`l` | LITERAL | `'[a]' ==~ /[a]/l`
|
|
|`m` | MULTILINE | `'a\nb\nc' =~ /^b$/m`
|
|
|`s` | DOTALL (aka single line) | `'a\nb\nc' =~ /.b./s`
|
|
|`U` | UNICODE_CHARACTER_CLASS | `'Ɛ' ==~ /\\w/U`
|
|
|`u` | UNICODE_CASE | `'Ɛ' ==~ /ɛ/iu`
|
|
|`x` | COMMENTS (aka extended) | `'a' ==~ /a #comment/x`
|
|
|=======================================================================
|
|
|
|
[float]
|
|
[[painless-deref]]
|
|
=== Dereferences
|
|
|
|
Like lots of languages, Painless uses `.` to reference fields and call methods:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
String foo = 'foo';
|
|
TypeWithGetterOrPublicField bar = new TypeWithGetterOrPublicField()
|
|
return foo.length() + bar.x
|
|
---------------------------------------------------------
|
|
|
|
Like Groovy, Painless uses `?.` to perform null-safe references, with the
|
|
result being `null` if the left hand side is null:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
String foo = null;
|
|
return foo?.length() // Returns null
|
|
---------------------------------------------------------
|
|
|
|
Unlike Groovy, Painless doesn't support writing to null values with this
|
|
operator:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
TypeWithSetterOrPublicField foo = null;
|
|
foo?.x = 'bar' // Compile error
|
|
---------------------------------------------------------
|
|
|
|
[float]
|
|
[[painless-operators]]
|
|
=== Operators
|
|
|
|
All of Java's https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html[operators] are
|
|
supported with the same precedence, promotion, and semantics.
|
|
|
|
There are only a few minor differences and add-ons:
|
|
|
|
* `==` behaves as Java's for numeric types, but for non-numeric types acts as https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object-[`Object.equals()`]
|
|
* `===` and `!==` support exact reference comparison (e.g. `x === y`)
|
|
* `=~` true if a portion of the text matches a pattern (e.g. `x =~ /b/`)
|
|
* `==~` true if the entire text matches a pattern (e.g. `x ==~ /[Bb]ob/`)
|
|
|
|
The `?:` (aka Elvis) operator coalesces null values. So `x ?: 0` is `0` if `x`
|
|
is `null` and whatever value `x` has otherwise. It is a convenient way to write
|
|
default values like `doc['x'].value ?: 0` which is 0 if `x` is not in the
|
|
document being processed. It can also work with null safe dereferences to
|
|
efficiently handle null in chains. For example,
|
|
`doc['foo.keyword'].value?.length() ?: 0` is 0 if the document being processed
|
|
doesn't have a `foo.keyword` field but is the length of that field if it does.
|
|
Lastly, `?:` is lazy so the right hand side is not evaluated at all if the left
|
|
hand side isn't null.
|
|
|
|
NOTE: Unlike Groovy, Painless' ++?:++ operator only coalesces `null`, not `false`
|
|
or http://groovy-lang.org/semantics.html#Groovy-Truth[falsy] values. Strictly
|
|
speaking Painless' ++?:++ is more like Kotlin's ++?:++ than Groovy's ++?:++.
|
|
|
|
NOTE: The result of `?.` and `?:` can't be assigned to primitives. So
|
|
`int[] someArray = null; int l = someArray?.length` and
|
|
`int s = params.size ?: 100` don't work. Do
|
|
`def someArray = null; def l = someArray?.length` and
|
|
`def s = params.size ?: 100` instead.
|
|
|
|
|
|
[float]
|
|
[[painless-control-flow]]
|
|
=== Control flow
|
|
|
|
Java's https://docs.oracle.com/javase/tutorial/java/nutsandbolts/flow.html[control flow statements] are supported, with the exception
|
|
of the `switch` statement.
|
|
|
|
In addition to Java's `enhanced for` loop, the `for in` syntax from groovy can also be used:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
for (item : list) {
|
|
...
|
|
}
|
|
---------------------------------------------------------
|
|
|
|
[float]
|
|
[[painless-functions]]
|
|
=== Functions
|
|
|
|
Functions can be declared at the beginning of the script, for example:
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
boolean isNegative(def x) { x < 0 }
|
|
...
|
|
if (isNegative(someVar)) {
|
|
...
|
|
}
|
|
---------------------------------------------------------
|
|
|
|
[float]
|
|
[[painless-lambda-expressions]]
|
|
=== Lambda expressions
|
|
Lambda expressions and method references work the same as https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html[Java's].
|
|
|
|
[source,painless]
|
|
---------------------------------------------------------
|
|
list.removeIf(item -> item == 2);
|
|
list.removeIf((int item) -> item == 2);
|
|
list.removeIf((int item) -> { item == 2 });
|
|
list.sort((x, y) -> x - y);
|
|
list.sort(Integer::compare);
|
|
---------------------------------------------------------
|
|
|
|
Method references to functions within the script can be accomplished using `this`, e.g. `list.sort(this::mycompare)`.
|