APL Data-Binding Syntax
APL data binding expressions occur inside JSON strings and take the form "${expression}
". Any number of expressions may occur inside a string, such as "${2}+${2} = ${2+2}"
. Expressions are evaluated within the current data-binding context. The data-binding context is a global dictionary that supports booleans, numbers, strings, arrays, objects, null, and references to defined resources.
- Supported value types
- Truthy and Coercion
- Operators
- Array and Object access
- Function calls
- Data-binding string conversion
Supported value types
Identifier
An identifier is a name used to identify a data-binding variable.
Identifiers must follow the C identifier naming convention:
[a-zA-Z_][a-zA-Z0-9_]*
. That is, the identifier must start with an
upper or lower-case ASCII letter or underscore and may be followed by
zero or more ASCII letters, numbers, or the underscore:
${data}
${_myWord23}
${__AnUgly26_letter__examplE}
String literals
Strings are defined using either single or double quotes. The starting and ending quote must match. Quotes, carriage returns, and line-feeds may be escaped.
${"Double-quoted string"}
${'Single-quoted string'}
${"Inner quote: \" or '"}
Expressions may be nested inside of a string.
${"Two plus two is ${2+2}"}
Numbers
Positive, negative, and floating point numbers are supported. Scientific notation is not supported. All numbers are internally stored as doubles:
${1}
${-34.75}
${64000000000}
Booleans
Boolean values of true
and false
are supported.
${true}
${false}
null
The null
constant is supported.
${null}
Resources
Resources defined in the data-binding context use the reserved character "@". For example:
${@myBlue}
${@isLandscape ? @myWideValue : @myNarrowValue}
APL packages define resources exposed in data-binding.
Absolute dimensions
A dimensional suffix converts a number into an absolute viewport dimension. The valid dimensional suffixes are "dp", "px", "vh", and "vw":
${23 dp} // 23 display-independent pixels
${10 px} // 10 pixels
${50 vw} // 50% of the width of the viewport
${100vh} // 100% of the height of the viewport
Dimensional suffixes must be attached to a number, not a more complex expression. For example ${(20*20) dp}
is not valid, but ${20*20dp}
is valid.
Truthy and Coercion
Data-binding expressions involve a number of different types. These types may be converted into other types. The following table gives an example of the different conversions (note that this assumes a viewport width of 512dp and a dpi of 320):
Object | Example | As Boolean | As Number | As String | As Color | As Dimension |
---|---|---|---|---|---|---|
Null | null | false | 0 | "" | transparent | 0dp |
Boolean | true | true | 1 | "true" | transparent | 0dp |
Boolean | false | false | 0 | "false" | transparent | 0dp |
Number | 23 | true | 23 | "23" | #00000017 | 23dp |
Number | 0 | false | 0 | "0" | transparent | 0dp |
String | "My dog" | true | 0 | "My dog" | transparent | 0dp |
String | "" | false | 0 | "" | transparent | 0dp |
String | "-2.3" | true | -2.3 | "-2.3" | transparent | -2.3dp |
String | "red" | true | 0 | "red" | #ff0000ff | 0dp |
String | "50vw" | true | 50 | "50vw" | transparent | 256dp |
Array | [] | true | 0 | "" | transparent | 0dp |
Map | {} | true | 0 | "" | transparent | 0dp |
Color | red | true | 0 | "#ff0000ff" | #ff0000ff | 0dp |
Dimension | 32px | true | 16 | "16dp" | transparent | 16dp |
Dimension | 0vh | false | 0 | "0dp" | transparent | 0dp |
Dimension | 23% | true | 0.23 | "23%" | transparent | 23% |
Dimension | 0% | false | 0 | "0%" | transparent | 0% |
Dimension | auto | true | 0 | "auto" | transparent | auto |
Anything else | ... | true | 0 | "" | transparent | 0dp |
Boolean coercion
A truthy value is a value that is considered true
when evaluated in a boolean context. All values are truthy except for false
, 0
, ""
, a zero
absolute or relative dimension, and null
.
Number coercion
The Boolean "true" value is converted to the number 1. String values
are converted using the C++ std::stod
method (note that this is
influenced by the locale). Absolute dimensions convert to the number of
dp in the absolute dimension; relative dimensions convert to the
percentage value (e.g., 32% -> 0.32). Everything else converts to 0.
String coercion
Internal types are converted to strings using the rules in the following table:
Object | Example | Result | Description |
---|---|---|---|
Null | null | '' |
The null value is not displayed. |
Boolean | true false | 'true' 'false' |
Boolean true & false are displayed as strings. |
Number | -23 | '-23' |
Integers have no decimal places. |
1/3 | '0.333333' |
Non-integers have decimal places. | |
String | "My "dog" " | 'My "dog" ' |
String values |
Array | [...] | '' |
Arrays are not displayed. |
Map | {...} | '' |
Maps are not displayed |
Color | red | '#ff0000ff' |
Colors are shown in #rrggbbaa format. |
Dimension | 23 dp | '20dp' |
Absolute dimensions are shown with the suffix 'dp' |
Dimension | 20 % | '20%' |
Percentage dimensions are shown with the suffix '%' |
Dimension | auto | 'auto' |
The auto dimension is shown as 'auto' |
Anything else | ${Math.min} |
'' |
Math functions are not shown. |
The specific format of non-integer numbers is not defined, but should
follow closely the C++ standard for sprintf(buf, "%f", value)
. It may
change based on the locale.
Color coercion
Color values are stored internally as 32-bit RGBA values. Numeric values will are treated as unsigned 32-bit integers and converted directly. String values are parsed according to the rules in Data Types - Color.
Absolute dimension coercion
Numeric values are assumed to be measurements in "dp" and are converted to absolute dimensions. String values are parsed according to the rules in Data Types - Dimension. All other values are 0.
Relative dimension coercion
Numeric values are assumed to be percentages and are converted directly. For example, 0.5 converts to 50%. Strings are parsed according to the rules in Data Types - Dimension. All other values are 0.
Operators
APL supports different types of operators: arithmetic, logical, comparison, and ternary.
Arithmetic operators
The standard arithmetic operations for addition, subtraction, multiplication, division, and remainder are supported:
${1+2} // 3
${1-2} // -1
${1*2} // 2
${1/2} // 0.5
${1%2} // 1.
Addition and subtraction work for pairs of numbers, absolute dimensions, and relative dimensions. When a number is combined with either an absolute or relative dimension, the number is coerced into the appropriate dimension.
The addition operator also acts as a string-concatenation operator if either the left or right operand is a string.
${27+''} // '27'
${1+' dog'} // '1 dog'
${'have '+3} // 'have 3'
Multiplication, division, and the remainder operator work for pairs of numbers. Multiplication also works if the one of the operands is a dimension (either relative or absolute) and the other is a number; the result is a dimension. Division also works if the first operand is a dimension (either relative or absolute) and the second is a number; the result is a dimension.
The remainder operator behaves as in JavaScript. That is,
${10 % 3} // 1
${-1 % 2} // -1
${3 % -6} // 3
${6.5 % 2} // 0.5
Logical operators
The standard logical and/or/not operators are supported.
${true || false} // true
${true && false} // false
${!true} // false
The &&
returns the first operand if it is not truthy and the second otherwise. The ||
operator returns the first operand if it is truthy and the second otherwise.
${7 && 2} // 2
${null && 3} // null
${7 || 2} // 7
${0 || -16} // -16
Comparison Operators
Comparison operators return boolean values.
${1 < 2}
${75 <= 100}
${3 > -1}
${4 >= 4}
${myNullValue == null}
${(2>1) == true}
${1 != 2}
The comparison operators do not apply to arrays and objects.
Null coalescing
The ??
operator is the null-coalescing operator. It returns the
left-hand operand if the operand is not null; otherwise it returns the
right-hand operand. The null-coalescing operator may be chained:
${person.name ?? person.surname ?? 'Hey, you!'}
The null-coalescing operator will return the left-hand operand if it is anything but null:
${1==2 ?? 'Dog'} // returns false
${1==2 || 'Dog'} // returns 'Dog'
Ternary Operator
The ternary conditional operator ${a ? b : c}
evaluates the left-hand operand. If it evaluates to true or a truthy value, the middle operand is returned. Otherwise the right-hand operand is returned.
${person.rank > 8 ? 'General' : 'Private'}
Array and Object access
Array
Array access uses the []
operator, where the operand should be an integer. Arrays also support the .length
operator to return the length of the array. Accessing an element outside of the array bounds returns null
.
${myArray[4]} // 5th element in the array (0-indexed)
${myArray.length} // Length of the array
${myArray[-1])} // Last element in the array
${myArray[myArray.length]} // Returns null (out of bounds)
Passing a negative index counts backwards through the array.
${a[-1] == a[a.length - 1]} // True
Object
Objects support the .
operator and the []
array access operator with string values.
${myObject.name} // The 'name' property of myObject
${myObject['name']} // The 'name' property of myObject
If the property is not defined, null
is returned.
Calling the .
or []
operator on null
returns null
.
${myNullObject.address.zipcode} // Returns null
The right-side operand of the dot operator must be a valid identifier.
Function calls
Data-binding supports a limited number of built-in functions. Functions are of the form:
functionName( arg1, arg2, … )
Functions do not have to have arguments. A function returns a single value. Some functional expressions are shown here:
${Math.floor(1.1)} // 1
${Math.ceil(1.2)} // 2
${Math.round(1.2)} // 1
${Math.min(1,2,3,4)} // 1
${Math.max(1,2,3,4)} // 4
${String.toUpperCase('Hello')} // HELLO
${String.toLowerCase('Hello')} // hello
${String.slice('Hello', 1, -1)} // ell
Built-in functions
Some mathematical and string functions are built in to APL.
Property | Description | Example |
Math.abs(x) | Return the absolute value of x | ${Math.abs(-2.3)} == 2.3 |
Math.acos(x) | The arccosine of x | ${Math.acos(1)} == 0 |
Math.asin(x) | The arcsine of x | ${Math.asin(0)} == 0 |
Math.atan(x) | The arctangent of x | ${Math.atan(1)} == 0.7853981633974483 |
Math.ceil(x) | Return the smallest integer greater than or equal to x. | ${Math.ceil(2.3)} == 3 |
Math.clamp(x,y,z) | Return x if y<x, z if y>z, and otherwise y. | ${Math.clamp(1, 22.3, 10)} == 10 |
Math.cos(x) | The cosine of x | ${Math.cos(0)} == 1 |
Math.floor(x) | Return the largest integer less than or equal to x. | ${Math.floor(2.3)} = 2 |
Math.max(x1,x2,…) | Return the largest argument | ${Math.max(2,3)} == 3 |
Math.min(x1,x2,…) | Return the smallest argument | ${Math.min(2,3)} == 2 |
Math.PI | The value of PI | 3.141592653589793 |
Math.random() | A random number between 0 and 1 | ${Math.random()} == 0.7113654073137101 (the actual random number returned will be different) |
Math.round(x) | Return the nearest integer to x | ${Math.round(2.3)} == 2 |
Math.sign(x) | The sign of x: -1, 0, or 1 | ${Math.sign(-43.1) == -1} |
Math.sin(x) | The sine of x | ${Math.sin(Math.PI/6)} == 0.5 |
Math.sqrt(x) | The square root of x | ${Math.sqrt(9)} == 3 |
Math.tan(x) | The tangent of x | ${Math.tan(Math.PI/4)} == 0.5 |
String.slice(x,y[,z]) | Return the subset of x starting at index y and extending to but not including index z. If z is omitted, the remainder of the string is returned. If y is a negative number, select from the end of the string. | ${String.slice('berry', 2, 4)} == 'rr' ${String.slice('berry', -2)} == 'ry' |
String.toLowerCase(x) | Return a lower-case version of x | ${String.toLowerCase('bEn')} == "ben" |
String.toUpperCase(x) | Return an upper-case version of x. | ${String.toUpperCase('bEn')} == "BEN" |
Data-binding string conversion
Because APL is serialized in JSON, all data-bound expressions are defined inside of a JSON string:
{
"MY_EXPRESSION": "${....}"
}
If there are no spaces between the quotation marks and the data-binding expression, then the result of the expression is the result of the data-binding evaluation. For example:
"${true}" -> Boolean true
"${2+4}" -> Number 6
"${0 <= 1 && 'three'}" -> String 'three'
When extra spaces are in the string outside of the data-binding expression or when two data-binding expressions are juxtaposed, the result is a string concatenation:
" ${true}" -> String ' true'
"${2+4} " -> String '6 '
"${2+1}${1+2}" -> String '33'