Expressions
Ceramic's expression hierarchy, from highest to lowest precedence:
| Level | Forms | Operator functions |
|---|---|---|
| Atomic | names, literals, (), [], __FILE__ etc., eval |
(none) |
| Suffix | a(b) a[b] a.0 a.field a^ |
call index staticIndex fieldRef dereference |
| Prefix | +a -a &a *a |
plus minus, address and dispatch are primitive |
| Multiplicative | a*b a/b a%b |
multiply divide remainder |
| Additive | a+b a-b |
add subtract |
| Ordered comparison | <= < > >= |
lesserEquals? lesser? greater? greaterEquals? |
| Equality | == != |
equals? notEquals? |
| Boolean | not a a and b a or b |
primitive, not overloadable |
| Low-precedence prefix | if (a) b else c, name: a, static a, ..a, a -> b |
(none) |
| Multiple value | a, b, c |
(none) |
Atomic Expressions
Name References
A bare identifier evaluates to the named local or global entity in the current scope. An error is raised if no match is found.
import a;
import a.(b);
var c = 0;
foo(d) {
var e = 0;
println(a, b, c, d, e);
}
Names bound to multiple values (variadic variables, variadic arguments) must be referenced with the .. unpack operator:
[..TT]
foo(..xs:TT) {
println(..xs, " have the types ", ..TT);
}
Literal Expressions
| Literal | Default type | Type suffix examples |
|---|---|---|
true / false |
Bool |
(none) |
1, 0xFF |
Int32 (or module default) |
ss s i l ll uss us u ul ull |
1.0, 1e2 |
Float64 (or module default) |
f ff fl fj j ffj flj |
'x' |
via Char operator |
(none) |
"hello" |
via StringConstant operator |
(none) |
#foo, #"foo" |
Static[#foo] |
(none) |
println(Type(1)); // Int32
println(Type(-1ss)); // Int8
println(Type(+1ul)); // UInt64
println(Type(1.0f)); // Float32
println(Type(1.j)); // Imag64
Integer type suffixes may be applied to floating-point literal tokens to produce a float of that type. Floating-point suffixes may not be applied to integer literal tokens.
Parentheses
(expr) overrides precedence. It has no other effect.
Tuple Expressions
[a, b, c] constructs a tuple by calling the tupleLiteral operator function.
Compilation Context Operators
These are only valid inside alias functions:
| Operator | Evaluates to |
|---|---|
__FILE__ |
Static string: the source file of the call site |
__LINE__ |
Int32: the source line |
__COLUMN__ |
Int32: the source column |
__ARG__ name |
Static string. Textual representation of argument name, not evaluated |
alias assert(cond:Bool) {
if (not cond) {
println(stderr, "Assertion \"", __ARG__ cond, "\" failed at ",
__FILE__, ":", __LINE__, ":", __COLUMN__);
flush(stderr);
abort();
}
}
Eval Expressions
eval expr evaluates a compile-time expression to a static string, parses it as an expression, and substitutes the result in place. The generated string must be a complete, parsable expression.
println(eval #""" "hello world" """);
Suffix Operators
Call (a(b, c))
If a is a symbol, argument types are matched to its overloads and the matching one is called. If a is a CodePointer, the pointed-to function is invoked. Otherwise, the call desugars to call(a, b, c).
Lambda expressions can be passed as trailing arguments with : / :: separators:
ifZero(rand()): () -> {
println("Reply hazy; try again")
} :: x -> {
println("Lucky number: ", x);
}
If any argument is prefixed with *, the call becomes a dynamic dispatch on a variant type.
Index (a[b, c])
Desugars to index(a, b, c). If a is a parameterized symbol, the operation is primitive: the symbol is instantiated for compile-time parameters.
var xs = Array[Int, 3](0, 111, 222);
println(xs[2]); // → index(xs, 2)
Static Index (a.0)
Desugars to staticIndex(a, static 0). Used for positional tuple field access.
var x = ["hello", "cruel", "world"];
println(x.0, ' ', x.2);
Field Reference (a.field)
Desugars to fieldRef(a, #"field"). Used for named field access on records. Overloading fieldRef lets you add custom accessors.
If a is an imported module name, the operation is primitive and looks up the name directly in that module's namespace.
import foo;
foo.bar(); // module field reference; primitive
var p = Point(array(1.0, 2.0));
println(p.x, p.y); // fieldRef(p, #"x"), fieldRef(p, #"y")
// Custom swizzle accessors:
overload fieldRef(p:Point, static #"xy") = ref p.coords[0], p.coords[1];
Dereference (a^)
Desugars to dereference(a). Used to get a reference to the value behind a pointer.
Prefix Operators
| Operator | Behavior |
|---|---|
+a |
desugars to plus(a) |
-a |
desugars to minus(a) |
&a |
primitive: returns Pointer[T] to a, which must be an lvalue. Not overloadable |
*a |
dispatch operator. Only valid as an argument to a call expression |
Dispatch (*a)
Transforms a call into dynamic dispatch on a variant type. Each instance type of the dispatched argument has an overload looked up and compiled into a dispatch table. All overloads must have matching return types and ref-ness.
variant Shape (Circle, Square);
overload draw(s:Circle) { println("()"); }
overload draw(s:Square) { println("[]"); }
drawShapes(ss:Vector[Shape]) {
for (s in ss)
draw(*s); // dispatches over Circle and Square
}
Arithmetic Operators
| Operator | Desugars to |
|---|---|
a * b |
multiply(a, b) |
a / b |
divide(a, b) |
a % b |
remainder(a, b) |
a + b |
add(a, b) |
a - b |
subtract(a, b) |
All arithmetic operators are left-associative within their precedence group.
Comparison Operators
| Operator | Desugars to |
|---|---|
a <= b |
lesserEquals?(a, b) |
a < b |
lesser?(a, b) |
a > b |
greater?(a, b) |
a >= b |
greaterEquals?(a, b) |
a == b |
equals?(a, b) |
a != b |
notEquals?(a, b) |
All comparison operators are left-associative within their precedence group.
Boolean Operators
| Operator | Behavior |
|---|---|
not a |
Complement. a must be Bool. Not overloadable |
a and b |
Short-circuit conjunction. Right-associative |
a or b |
Short-circuit disjunction. Right-associative |
Both and and or require Bool operands and are not overloadable.
Low-Precedence Prefix Operators
If Expressions
if (condition) thenExpr else elseExpr
Both branches must have the same type. Unlike if statements, the else clause is required.
Keyword Pair Expressions
name: expr is sugar for [#"name", expr]: a tuple with a static string key. Useful for named-parameter style arguments to higher-order functions.
Static Expressions
static expr evaluates expr at compile time and wraps the result in Static[result]. Used to pass compile-time values to static arguments. Applied to a symbol or static string, it is a no-op.
log(static LOG, "starting program");
Unpack (..a)
Evaluates a in multiple-value context and interpolates its values into the surrounding expression list.
twoThroughFour() = 2, 3, 4;
oneThroughFive() = 1, ..twoThroughFour(), 5;
Lambda Expressions
An anonymous function: argument list, arrow, body.
var squares = mapped(x -> x*x, range(10));
Two capture modes:
->: captures by reference. Mutations are visible outside the lambda. The lambda must not outlive its enclosing scope.=>: captures by copying. The lambda is independent of its origin scope.
// by reference; sum accumulates outside
var sum = 0;
var squares = mapped(x -> { var sq = x*x; sum += sq; return sq; }, range(10));
// by value; closure is self-contained
curriedAdd(x) = y => x + y;
var plus3 = curriedAdd(3);
A lambda with a single untyped argument may omit parentheses: x -> x*x. A lambda that does not capture is equivalent to an anonymous named function.
Lambda has higher precedence than the multi-value comma. a -> b, c parses as (a -> b), c. To return multiple values from a lambda, use a block body or explicit parentheses: a -> (b, c).
Multiple Value Expressions
Most Ceramic functions can return multiple values. The comma operator builds a multiple-value list:
twoThroughFour() = 2, 3, 4;
Expressions are normally constrained to a single value. To use a multiple-value expression inside another expression, unpack it with ..:
oneThroughFive() = 1, ..twoThroughFour(), 5; // ok
oneThroughFive() = 1, twoThroughFour(), 5; // ERROR
The following contexts provide implicit multiple-value context at the outermost level. No .. is needed there:
- Expression statements
- Local variable bindings with multiple variables
- Assignment with multiple left-hand values
..forvalue lists
Within a concatenating expression, sub-expressions still require explicit ..:
var a, b, c, d = 0, ..oneTwoThree(); // .. required inside concat
..for (i in ..oneTwoThree(), ..fourFiveSix())
println(i);