# Sapphire Expression Language

The expression language provides a convenient way to embed a bit of custom logic in various parts of Sapphire without resorting to Java. Expressions can be used in some of the modeling annotations as well as in UI definitions.

The syntax and semantics borrow extensively from JSP expression language and its precursors, however there are a number of deviations. This document is the definitive reference.

- Expressions
- Literals
- Child Property Operator
- Arithmetic Operators
- Relational Operators
- Logical Operators
- Empty Operator
- Conditional Operator
- Parentheses
- Operator Precedence
- Functions
- Enums
- Type Conversion
- String Concatenation

## Expressions

There are three types of expressions: literal expressions, eval expressions and composite expressions. A literal expression is simply a string such as `Abc`. No quotation is necessary. An eval expression is anything enclosed between `${` and `}`. The syntax and semantics of eval expressions is discussed in detail in the following sections. A composite expression is one that combines zero or more literal expressions with one or more eval expressions.

The following are all examples of valid expressions:

- `Marry had a little lamb.`
- `${ Name }`
- `${ Name } had a little ${ Animal }.`
- `${ Name == null ? "[contact]" : Name }`
- `${ ( Month == 'February' ? ( LeapYear ? 29 : 28 ) : 30 ) * EmployeeCount }`

## Literals

There are literals for boolean, integer, floating point, string, and null in the expression language.

- Boolean

- `BooleanLiteral ::= 'true' | 'false'`

- Integer

- `IntegerLiteral ::= ['0'-'9']+`

- Floating Point

- `FloatingPointLiteral ::= (['0'-'9'])+ '.' (['0'-'9'])* Exponent? | '.' (['0'-'9'])+ Exponent? | (['0'-'9'])+ Exponent?`
- `Exponent ::= ['e','E'] (['+','-'])? (['0'-'9'])+`

- String

- `StringLiteral ::= '([^'\]|\'|\\)*' | "([^"\]|\"|\\)*"`
- With single and double quotes - `"` is escaped as `\"`, `'` is escaped as `\'`, and `\` is escaped as `\\`. Quotes only need to be escaped in a string value enclosed in the same type of quote.

- Null

- `NullLiteral ::= 'null'`

## Child Property Operator : A.B and A["B"]

The expression language follows ECMAScript in unifying the treatment of the `.` and `[]` operators. So `expr-a.identifier-b` is equivalent to `expr-a["identifier-b"]`.

## Arithmetic Operators

Arithmetic operators are provided to act on integer (`BigInteger` and `Long`) and floating point (`BigDecimal` and `Double`) values.

- Addition
- Subtraction
- Multiplication
- Division
- Remainder (Modulo)
- Unary Minus

The evaluation of arithmetic operators is described in the following sections. A and B are the evaluation of subexpressions.

### Addition : A + B

- If A and B are `null`, return `(Long) 0`.
- If A or B is a `BigDecimal`, coerce both to `BigDecimal` and return `A.add( B )`.
- If A or B is a `Float`, `Double`, or `String` containing `.`, `e`, or `E`:

- If A or B is `BigInteger`, coerce both A and B to `BigDecimal` and apply operator.
- Otherwise, coerce both A and B to `Double` and apply operator.

- If A or B is `BigInteger`, coerce both to `BigInteger` and return `A.add( B )`.
- Otherwise coerce both A and B to `Long` and apply operator.

### Subtraction : A - B

- If A and B are `null`, return `(Long) 0`.
- If A or B is a `BigDecimal`, coerce both to `BigDecimal` and return `A.subtract( B )`.
- If A or B is a `Float`, `Double`, or `String` containing `.`, `e`, or `E`:

- If A or B is `BigInteger`, coerce both A and B to `BigDecimal` and apply operator.
- Otherwise, coerce both A and B to `Double` and apply operator.

- If A or B is `BigInteger`, coerce both to `BigInteger` and return `A.subtract( B )`.
- Otherwise coerce both A and B to `Long` and apply operator.

### Multiplication : A * B

- If A and B are `null`, return `(Long) 0`.
- If A or B is a `BigDecimal`, coerce both to `BigDecimal` and return `A.multiply( B )`.
- If A or B is a `Float`, `Double`, or `String` containing `.`, `e`, or `E`:

- If A or B is `BigInteger`, coerce both A and B to `BigDecimal` and apply operator.
- Otherwise, coerce both A and B to `Double` and apply operator.

- If A or B is `BigInteger`, coerce both to `BigInteger` and return `A.multiply( B )`.
- Otherwise coerce both A and B to `Long` and apply operator.

### Division : A { /, div } B

- If A and B are `null`, return `(Long) 0`.
- If A or B is a `BigDecimal` or a `BigInteger`, coerce both to `BigDecimal` and return `A.divide( B, BigDecimal.ROUND_HALF_UP )`.
- Otherwise, coerce both A and B to `Double` and apply operator.

### Remainder (Modulo) : A { %, mod } B

- If A and B are `null`, return `(Long) 0`.
- If A or B is a `BigDecimal`, `Float`, `Double`, or `String` containing `.`, `e`, or `E`, coerce both A and B to `Double` and apply operator.
- If A or B is a `BigInteger`, coerce both to `BigInteger` and return `A.remainder( B )`.
- Otherwise coerce both A and B to `Long` and apply operator.

### Unary Minus : -A

- If A is `null`, return `(Long) 0`.
- If A is a `BigDecimal` or `BigInteger`, return `A.negate()`.
- If A is a `String`:

- If A contains `.`, `e`, or `E`, coerce to a `Double` and apply operator.
- Otherwise, coerce to a `Long` and apply operator.

- If A is `Byte`, `Short`, `Integer`, `Long`, `Float`, or `Double`, retain type and apply operator.
- Otherwise, error.

## Relational Operators

Relational operators are provided to compare two values.

- Equal
- Not Equal
- Less Than
- Less Than or Equal
- Greater Than
- Greater Than or Equal
- In

The evaluation of relational operators is described in the following sections. A and B are the evaluation of subexpressions.

### Equal : A { ==, eq } B

- If A `==` B, return `true`.
- If A is `null` or B is `null` return `false`.
- If A or B is `BigDecimal`, coerce both A and B to `BigDecimal` and return `A.equals( B )`.
- If A or B is `Float` or `Double` coerce both A and B to `Double`, apply operator.
- If A or B is `BigInteger`, coerce both A and B to `BigInteger` and return `A.equals( B )`.
- If A or B is `Byte`, `Short`, `Character`, `Integer`, or `Long` coerce both A and B to `Long`, apply operator.
- If A or B is `Boolean` coerce both A and B to `Boolean`, apply operator.
- If A or B is an enum, coerce both A and B to enum, apply operator.
- If A or B is `String` coerce both A and B to `String`, compare lexically.
- Otherwise, return `A.equals( B )`.

### Not Equal : A { !=, ne } B

- If A `==` B, return `false`.
- If A is `null` or B is `null` return `true`.
- If A or B is `BigDecimal`, coerce both A and B to `BigDecimal` and return `! A.equals( B )`.
- If A or B is `Float` or `Double` coerce both A and B to `Double`, apply operator.
- If A or B is `BigInteger`, coerce both A and B to `BigInteger` and return `! A.equals( B )`.
- If A or B is `Byte`, `Short`, `Character`, `Integer`, or `Long` coerce both A and B to `Long`, apply operator.
- If A or B is `Boolean` coerce both A and B to `Boolean`, apply operator.
- If A or B is an enum, coerce both A and B to enum, apply operator.
- If A or B is `String` coerce both A and B to `String`, compare lexically.
- Otherwise, return `! A.equals( B )`.

### Less Than : A { <, lt } B

- If A is `null` or B is `null`, return `false`.
- If A or B is `BigDecimal`, coerce both A and B to `BigDecimal` and use the return value of `A.compareTo( B )`.
- If A or B is `Float` or `Double` coerce both A and B to `Double` apply operator.
- If A or B is `BigInteger`, coerce both A and B to `BigInteger` and use the return value of `A.compareTo( B )`.
- If A or B is `Byte`, `Short`, `Character`, `Integer`, or `Long` coerce both A and B to `Long` and apply operator.
- If A or B is `String` coerce both A and B to `String`, compare lexically.
- If A is `Comparable`, then use result of `A.compareTo( B )`.
- If B is Comparable, then use result of `B.compareTo( A )`.
- Otherwise, error.

### Less Than or Equal : A { <=, le } B

- If A `==` B, return `true`.
- If A is `null` or B is `null`, return `false`.
- If A or B is `BigDecimal`, coerce both A and B to `BigDecimal` and use the return value of `A.compareTo( B )`.
- If A or B is `Float` or `Double` coerce both A and B to `Double` apply operator.
- If A or B is `BigInteger`, coerce both A and B to `BigInteger` and use the return value of `A.compareTo( B )`.
- If A or B is `Byte`, `Short`, `Character`, `Integer`, or `Long` coerce both A and B to `Long` and apply operator.
- If A or B is `String` coerce both A and B to `String`, compare lexically.
- If A is `Comparable`, then use result of `A.compareTo( B )`.
- If B is Comparable, then use result of `B.compareTo( A )`.
- Otherwise, error.

### Greater Than : A { >, gt } B

- If A is `null` or B is `null`, return `false`.
- If A or B is `BigDecimal`, coerce both A and B to `BigDecimal` and use the return value of `A.compareTo( B )`.
- If A or B is `Float` or `Double` coerce both A and B to `Double` apply operator.
- If A or B is `BigInteger`, coerce both A and B to `BigInteger` and use the return value of `A.compareTo( B )`.
- If A or B is `Byte`, `Short`, `Character`, `Integer`, or `Long` coerce both A and B to `Long` and apply operator.
- If A or B is `String` coerce both A and B to `String`, compare lexically.
- If A is `Comparable`, then use result of `A.compareTo( B )`.
- If B is Comparable, then use result of `B.compareTo( A )`.
- Otherwise, error.

### Greater Than or Equal : A { >=, ge } B

- If A `==` B, return `true`.
- If A is `null` or B is `null`, return `false`.
- If A or B is `BigDecimal`, coerce both A and B to `BigDecimal` and use the return value of `A.compareTo( B )`.
- If A or B is `Float` or `Double` coerce both A and B to `Double` apply operator.
- If A or B is `BigInteger`, coerce both A and B to `BigInteger` and use the return value of `A.compareTo( B )`.
- If A or B is `Byte`, `Short`, `Character`, `Integer`, or `Long` coerce both A and B to `Long` and apply operator.
- If A or B is `String` coerce both A and B to `String`, compare lexically.
- If A is `Comparable`, then use result of `A.compareTo( B )`.
- If B is Comparable, then use result of `B.compareTo( A )`.
- Otherwise, error.

### In : A in B

- If A is `null` or B is `null` return `false`.
- Otherwise, return `true` if `A == B[ i ]` for any value of `i` between `0` and `B.Size - 1`.

## Logical Operators

Logical operators are provided to perform boolean logic.

- And
- Or
- Not

The evaluation of logical operators is described in the following sections. A and B are the evaluation of subexpressions.

### And : A { &&, and } B

- Coerce both A and B to `Boolean`, apply operator.

### Or : A { ||, or } B

- Coerce both A and B to `Boolean`, apply operator.

### Not : A { !, not } B

- Coerce A to `Boolean`, apply operator.

## Empty Operator : empty A

The empty operator is a prefix operator that can be used to determine if a value is `null` or empty.

- If A is `null`, return `true`.
- if A is the empty `String`, then return `true`.
- if A is an empty array, then return `true`.
- if A is an empty `Collection`, return `true`.
- Otherwise, return `false`.

## Conditional Operator : A ? B : C

Return B or C, depending on the result of the evaluation of A.

- Coerce A to `Boolean`:

- If A is `true`, return B.
- If A is `false`, return C.

## Parentheses

Parentheses can be used to change precedence, as in: `${(a*(b+c))}`.

## Operator Precedence

Highest to lowest, left to right.

- `[] .`
- `()`
- `- (unary) not ! empty`
- `* / div % mod`
- `+ - (binary)`
- `< > <= >= lt gt le ge`
- `== != eq ne in`
- `&& and`
- `|| or`
- `? :`

Qualified functions with a namespace prefix have precedence over the operators. Thus the expression `${c?b:f()}` is illegal because `b:f()` is being parsed as a qualified function instead of part of a conditional expression. As usual, `()` can be used to make the precedence explicit, e.g `${c?b:(f())}`.

## Functions

The expression language supports functions, whose names can be qualified with a namespace.

The full syntax of qualified n-ary function is as follows:

`[ns:]f([a1[,a2[,...[,an]]]])`

Where `ns` is the namespace prefix, `f` is the name of the function, and `a` is an argument.

The following functions are available for use:

##functions##

Custom functions can be contributed via Sapphire Extension System.

## Enums

The expression language supports Java enumerations. Coercion rules for dealing with enumerated types are included in the following section. Also, when referring to values that are instances of an enumerated type from within an expression, use the literal string value to cause coercion to happen via the below rules. For example, Let's say we have an enum called Suit that has members Heart, Diamond, Club, and Spade. Furthermore, let's say we have a reference in the expression, mySuit, that is a Spade. If you want to test for equality with the Spade enum, you would say `${mySuit == 'Spade'}`. The type of the mySuit will trigger the invocation of `Enum.valueOf( Suit.class, "Spade" )`.

## Type Conversion

Every expression is evaluated in the context of an expected type. The result of the expression evaluation may not match the expected type exactly, so the rules described in the following sections are applied.

### Coerce A to String

- If A is `String`, return A.
- If A is `null`, return `""`.
- If A is `Enum`, return `A.name()`.
- If A is `array`, `List` or `Set`, return concatenation of items separated by a comma (,) character.
- Otherwise, return `A.toString()`.

### Coerce A to Number type N

- If A is `null` or `""`, return `0`.
- If A is `Character`, convert A to `new Short( (short) a.charValue() )`, and apply the following rules.
- If A is `Boolean`, then error.
- If A is `Number` type N, return A.
- If A is `Number`, coerce quietly to type N using the following algorithm:

- If N is `BigInteger`:

- If A is a `BigDecimal`, return `A.toBigInteger()`.
- Otherwise, return `BigInteger.valueOf( A.longValue() )`.

- If N is `BigDecimal`:

- If A is a `BigInteger`, return `new BigDecimal( A )`.
- Otherwise, return `new BigDecimal( A.doubleValue() )`.

- If N is `Byte`, return `new Byte( A.byteValue() )`.
- If N is `Short`, return `new Short( A.shortValue() )`.
- If N is `Integer`, return `new Integer( A.intValue() )`.
- If N is `Long`, return `new Long( A.longValue() )`.
- If N is `Float`, return `new Float( A.floatValue() )`.
- If N is `Double`, return `new Double( A.doubleValue() )`.
- Otherwise, error.

- If A is `String`, then:

- If N is `BigDecimal` then return `new BigDecimal( A )`.
- If N is `BigInteger` then return `new BigInteger( A )`.
- Otherwise, return `N.valueOf( A )`.

- Otherwise, error.

### Coerce A to Character

- If A is `null` or `""`, return `(char) 0`.
- If A is `Character`, return A.
- If A is `Number`, coerce quietly to type `Short`, then return a `Character` whose numeric value is equivalent to that of a `Short`.
- If A is `String`, return `A.charAt( 0 )`.
- Otherwise, error.

### Coerce A to Boolean

- If A is `null` or `""`, return `false`.
- If A is a `Boolean`, return A.
- If A is a `String`, return `Boolean.valueOf( A )`.
- Otherwise, error.

### Coerce A to an Enum Type T

- If A is `null`, return `null`.
- If A is assignable to T, return A.
- If A is `""`, return `null`.
- If A is a `String`, return `Enum.valueOf( T.getClass(), A )`.
- Otherwise, error.

### Coerce A to List

- If A is `null`, return `null`.
- If A is `List`, return A.
- If A is `Collection`, return list of collection items.
- If A is `array`, return list of array items.
- If A is `String`, then:

- If A is `""`, return empty list.
- Otherwise, break A into segments on comma (,) character and return list of these segments.

- Otherwise, return singleton list containing A.

### Coerce A to Any Other Type T

- If A is `null`, return `null`.
- If A is assignable to T, return A.
- If A is `""`, return `null`.
- If A is convertable to T using MasterConversionService, return the result.
- Otherwise, error.

## String Concatenation

The subject of string concatenation in an expression can be a source of confusion. In particular, expressions like `${ "A" + "B" }` cause an error during evaluation. That's because the `+` operator is exclusively arithmetic addition. It is not overloaded to also mean string concatenation, like it is in some other languages such as Java.

String concatenation in an expression can be accomplished in one of two ways:

- Composite expressions: `On the year ${ Year % 100 } of ${ Year / 100 } century.`
- Using the included concat function: `${ OnlyShowYear ? Year : concat( Year, "-", Month, "-" Day ) }`

Using the `concat` function is particularly necessary in cases where the result of concatenation should feed into another function or operator.

Copyright (c) 2016 Oracle
Content made available under the terms of Eclipse Public License.