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.

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.

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())}.

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:

Name

Description

Concat

Concatenates two or more strings into a single string. Particularly useful in contexts where composite expressions cannot be used, such as where the result of concatenation needs to feed into another function or operator.

FirstSegment

Breaks a string into segments using the provided separators and returns the first segment. If no separators are found in the string, the entire string is returned. This function takes two string operands. The first is the string to tokenize. The second is a sequence of characters that individually should be treated as valid separators.

LastSegment

Breaks a string into segments using the provided separators and returns the last segment. If no separators are found in the string, the entire string is returned. This function takes two string operands. The first is the string to tokenize. The second is a sequence of characters that individually should be treated as valid separators.

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" ).

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().

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 Any Other Type T

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.