The syntax of Expressions in ELaine (spiralcraft.lang) is roughly inspired by Java(tm) language expressions, with some major enhancements to support the contextual model and other advanced functionality.
The below presentation is intended to convey common usage patterns for the language constructs, and is not a formal specification of the grammar.
| String | x = "Hi"x = " \"Escaped quotes \" " x = " Escaped backslash \\ " |
|||
| Character | x = 'a' |
|||
| Integer | x = 1 |
|||
| Double | x = 1.0 |
|||
| Long | x = 1L |
|||
| Double | x = 1D |
|||
| Float | x = 1F |
|||
| true | x = true |
|||
| false | x = false |
|||
| null | x = null |
|||
| Expression | expr = `x.y.z == "test"` |
An Expression literal allows an unbound expression to be passed to other code where it can be bound into a different context. Namespace prefix expansions, however, will use the Namespace context in which the Expression is parsed, which is the same as the containing Expression. |
( expr ) |
Sub-Expression | x * (y + z) |
Change precedence by grouping terms |
num1 + num2 |
Add | x + y |
||
num1 - num2 |
Subtract | x - y |
||
num1 * num2 |
Multiply | x * y |
||
num1 / num2 |
Divide | x / y |
||
num1 % num2 |
Modulus | x % y |
bool1 && bool2 |
Logical AND | x && y |
y will not be evaluated when x is false | |||
bool1 || bool2 |
Logical OR | x || y |
y will not be evaluated when x is true | |||
bool1 ^ bool2 |
Exclusive OR | x ^ y |
||||
! bool1 |
Negate | !x |
||||
bool1 ? trueResult : falseResult |
Conditional | x ? a : b |
expression1 == expression2 |
Equals | x == y |
performs Object.equals() | |||
expression1 != expression2 |
Not Equal | x != y |
performs Object.equals() | |||
expression1 < expression2 |
Less than | x < y |
||||
expression1 <= expression2 |
Less than or equals | x <= y |
||||
expression1 > expression2 |
Greater than | x > y |
||||
expression1 >= expression2 |
Greater than or equals | x >= y |
||||
iterableExpression ?= itemExpression |
Contains | setX ?= y |
object . propName |
Dereference property | x.y |
||||
object . methodName ( param1 , ...) |
Method call: standard | x.y(p1,p2,...) |
||||
object . methodName ( param1 , ... , binding1 , ...) |
Method call: bound params | x.y(p1, p2, np1:=a, np2:=b, ...) |
See extended syntax | |||
functor .( param1 , ... , binding1 , ...) |
Functor call | myFunctor.(p1, p2, np1:=a, np2:=b, ...) |
See extended syntax |
iterX [ integerX ] |
Select: element at index | x[index] |
||||
iterX [ booleanX ] |
Select: filtered subset | x[ .y == z ] |
See extended syntax | |||
mapX [ keyX ] |
Select: associative mapping | x[ someKey ] |
See extended syntax | |||
iterX [ integerX .. integerX ] |
Select: inclusive range | x[ start .. end ] |
See extended syntax | |||
iterX [ integerX .! integerX ] |
Select: exclusive range | myPages[ mark .! mark+5 ] |
See extended syntax | |||
iterX.@top |
Top of sequence | finished.@top |
First item in sequence, or null if empty | |||
iterX #{ xlation } |
Map | myFriends#{ .name } |
One-to-one mapping into an array of expression result | |||
iterX $[ expression ] |
Reduce: distinct | myCars$[ .color ] |
Reduce to array with distinct values of expression result | |||
iterX $[ accumulatorX ] |
Reduce: accumulate | myOrders$[ .amount.[*fn:Sum] ] |
Reduce to single result computed from all collection values. See details. |
[ ns : typeName ] |
Resolve Focus (namespace relative) | [myns:MyWidget].someProperty |
Find nearest reference matching specified type URI in the focus chain (see Focus Resolution) | |||
[ : typeURI ] |
Resolve Focus (absolute) | [:class:/mypkg/MyWidget].someProperty |
Find nearest reference matching specified type URI in the focus chain (see Focus Resolution) | |||
expression { expression , ... } |
Subcontext | complexResult { .resultPropX * .resultPropY } |
Redefine the subject (leading '.') to an intermediate result for a list of expressions Return the result of the last expression in the list. |
location = expr |
Assign | x = y + z |
sets x to and returns y+z | |||
location := expr |
Bind | myParam := y + z |
Declares a binding for a parameter, field or property name. Only valid in specific constructs. |
stringExpression + stringExpression |
Concatenate String | "Foo" + "Bar" |
||
collectionExpression + collectionExpression |
Concatenate a Collection | myCollection + anotherCollection |
See Struct
{ structField , ... } |
Struct Definition (anonymous) | { firstName:="John" , lastName:="Doe" } |
||
{ [#uri] structField , ... } |
Struct Definition (named) | { [#myns:Person] firstName:="John" , lastName:="Doe" } |
||
fieldName := expression |
Struct Field (bound) | firstName:="John" |
||
fieldName : constTypeExpression |
Struct Field (unassigned) | firstName:[@j:String] |
[@ ns : typeName ] |
Resolve type (namespace relative) | [@j:Integer].parseInt("1") |
Resolve a type (a Reflector object) by its URI. See Type Reflection | |||
[@ : typeURI ] |
Resolve type (absolute) | [@:class:/java/lang/Integer].parseInt("1") |
Resolve a type (a Reflector object) by its URI. See Type Reflection | |||
expression.@type |
Inferred type | myObject.myProp.@type |
The bound type of an expression (constant for binding) | |||
expression.@subtype |
Dynamic type | heterogenousCollection[index].@subtype |
The type of data actually returned by the expression | |||
expression.@cast(typeExpression) |
Cast | objectHolder.value.@cast([@myns:MyType]) |
The bound type of an expression | |||
typeExpression.@nil |
Nil channel | [@myns:MyType].@nil |
Empty channel for a specified type |
[* ns : typeName { param1 , ... , binding1, ... } ] |
Constant object literal | [*myns:MyComponent { "superWidget5000", coolFactor:=11 } ].makeItHappen("bigStuff") |
Create in-place singleton | |||
expression.[* ns : typeName { param1 , ... , binding1, ... } ] |
Constant function literal | result.[*myns:MyComputation { precision:=0.0001D } ] * adjustmentFactorresult.[*myns:MyFunctor { precision:=0.0001D } ].(someOtherInput) |
Incorporate a function into channel chain (see detail) |
The syntax of spiralcraft.lang is designed to:
These operations require a basic understanding of the Focus chain and the Contextual Singleton pattern.
ELaine (spiralcraft.lang) uses a URI-based naming mechanism to uniquely identify types. This allows an Expression to reference types from multiple type models, such as the built-in reflection type model and the DALai data type model.
Type name URIs are used for a number of purposes:
To keep redundancy at a minimum, type names can either be explicitly specified or can utilize contextual namespace mappings as declared in developer artifacts such as XML files.
To use the explicit form, place a colon before the full URI (note the colon before the URI):
:class:/java/lang/String
To use the namespace:name form, define the namespace prefix to a pathed URI using the relevant mechanism. The resulting URI will be the mapped URI with the local name added as a path segment. The following translates to the URI "class:/java/lang/String"
j:String
Resolve an object in the Focus chain that has a type specified by a type name URI. Used to obtain references to various objects published by the container.
For example, Expressions are used to bind controls to "model" objects, where "model" is defined as what is reachable through the Focus chain (ie., anything made available by the application).
When an Expression is bound (which usually happens once, at load time), the Focus against which the expression is bound is queried for the specified URI. If neither subject of the Focus nor any local singletons are a match, the parent Focus is queried until the chain is exhausted. Typically, the URI is converted to a native representation and any subtypes or implementations will satisfy the query.
This resolution mechanism is the basis of the Contextual Singleton pattern.
Usage may take the form of [namespace:name] , where name is resolved against the URI mapped to namespace, or may take the form [:uri], where the uri is in absolute form.
The URI resolves against all type models in the Focus chain, including the built-in mapping to the Java class namespace.
[myns:MyWidget].someProperty
[:class:/myPackage/MyWidget].someProperty
An Expression is always bound against a Focus. This Focus is referred to as the "context" of the Expression. Un-prefixed names and method calls anywhere in an Expression refer to the context, for example:
name
doSomething(10)
The value exposed as the "context" normally remains constant for the duration of expression evaluation.
At all times within an Expression, a "subject" is defined, and can be referred to using the dot (".") prefix. Initially, the "subject" is the same as the "context".
Operators which involve constructs inside of angle brackets ( [ ... ] ) or curly braces ( { ... } ) usually re-define the "subject" for constructs within the scope of the brackets. This is known as "telescoping".
Telescoping allows us to form concise expressions based on iterative actions, for instance. The following example selects all members of mySet where the name of the color is "blue". mySet refers to some attribute of the expression "context", and .color refers to the property of the element of the mySet under examination.
mySet[.color.name=="blue"]
When nested telescoping constructs come into play, leading dots "." can be chained to refer to subjects of outer containers. For example, telescoping also allows us to "enclose" an intermediate result to avoid re-evaluation. In this example, a costlyArray is computed once, and the last 5 elements (as a range select) are returned.
costlyArray { .[ ..@size-5 .! ..@size ] }
Another example
customer.orders[.amount>customer.threshold]
In the preceding example, the .amount identifier refers to order object in the order list (the subject within the telescope), while the customer identifier refers to the outer context in which the expression was evaluated.
In some cases, an application component will bind an expression into an existing telescope. In the following simple example, a telescoped subject is assumed. The example represents the use of an expression as the criteria of a parameterized DALai Query, where the context supplies the parameters, and the subject represents the object being queried.
.name==nameParam && .dateOfBirth==dateOfBirthParam
In the preceding example, the .name identifier refers to the item of the collection being searched, where the nameParam identifier refers to the query parameter context (eg. a value entered by a user)
The array subscript operator supports extended operations on arrays and collections.
Basic indexing:
x[i] // i is an integer expression
Basic indexing, error case:
x[outOfBounds] // returns null
Boolean search through telescoped closure- returns same collection type as x but only matching items
x[<condition>] x[.y>z] x[.id==id][0] // unique condition, return null if not found
Associative:
x[<key>] // x is a map-like object
Resolve the Reflector for the type associated with a type URI.
This is similar to Focus resolution, but the type metadata is accessed by querying registered TypeModels and obtaining an instance of a Reflector, instead of the Contextual Singleton object that implements the specified type.
[@:class:/myPackage/MyWidget]
[@myns:MyWidget]
Static methods are members of Java classes, not the objects they instantiate, this they must be accessed through a type reference.
The Type Reflection operator provides access to the BeanReflector (the type Reflector associated with Java reflection) for the Java class.
For Java classes, the BeanReflector maps static class members into the "@" prefixed meta-namespace.
[@:class:/myPackage/MyWidget].@myStaticMethod(param1,param2) [@myns:MyWidget].@myStaticMethod(param1,param2)
x.@cast([@mytypes:MySubtype])
The actual type of the value returned from the expression. null is returned if the value is null.
Use with @cast and Type Focus to tailor EL aware content for specific subtypes (ie. access subtype properties).
x.@subtype
x.@subtype==[@mytypes:MySubtype]
The type (ie. tbe spiralraft.lang.Reflector instance) that will be returned by the sub-expression.
x.@type
Structs provide a convenient way to return complex data from an expression without having to define a globally referenceable type in some type model.
A Struct has one or more fields. Each field may have a declared type and/or may have its value derived from an expression evaluated when the Struct is created.
In a Struct, the subject is telescoped to the Struct itself. Internal fields can be accessed using a single dot (".") prefix.
The following Struct returns some information about a String, including the sum of the Integer values. Note that the first outer curly brace suffixes myString and defines a subcontext which telescopes to that expression (myString) and the inner curly brace defines the struct itself. The second subcontext (curly brace construct ) telescopes the Struct instance we just created, and performs a computation on its members.
myString
{
{
length:=..length
,chars:=..characters
,sumChars:=.chars$[ .[*fn:Sum] ]
}
}
{ .sumChars * .length
}
Returns a reference to the Channel that represents the bound sub-expression.
Useful for reporting metadata about bound expressions, and for debugging purposes.
x.@channel.myMetaData
A reference to the current Focus against which the sub-expression will be bound. Useful for querying metadata about application structure.
x.@focus x.@focus.focusChain