Expressions¶
Quill expressions are Go-flavored: infix arithmetic, pipe filters, arrow
functions, and null-safe access. The grammar is Pratt-parsed. An expression
appears anywhere in code (inside {{ ... }}, inside a statement head, inside
#{ ... }, and nested in another expression) and always evaluates to a single
dynamic value (Types). The runtime rules for equality, ordering,
coercion, and arithmetic are in Types; this page names the
operators and their surface.
Pipe filters¶
| pipes the left value into a filter as its first argument: x | upper is
upper(x); x | f(a, b) is f(x, a, b). Filters chain left to right:
x | trim | upper. The pipe is an ordinary expression operator, so a filtered
value may appear anywhere an expression may, not only at output sites.
The pipeline is the spine of the collection algebra, composing with arrow functions and the spaceship comparator into a Unix-style pipeline:
{{ users
| filter((u) => u.active)
| sort((a, b) => a.rank <=> b.rank)
| map((u) => u.name | upper)
| join(", ") }}
Because | is the filter operator, bitwise OR is the word b_or (alias |||),
which keeps the pipe unambiguous.
The precedence ladder¶
Quill publishes its own precedence numbers; only the relative ordering is binding. Higher binds tighter (evaluated first).
| Level | Operators | Assoc |
|---|---|---|
| 17 | postfix: . ?. [ ] ?[ ] ( ) call | filter |
left |
| 16 | prefix: not (!) - + ... spread |
right |
| 15 | ** power |
right |
| 14 | * / // % |
left |
| 13 | + - |
left |
| 12 | ~ concat |
left |
| 11 | .. range |
left |
| 10 | comparison/membership/test: == != < > <= >= <=> in not in matches starts with ends with has some has every is is not |
non-assoc |
| 9 | b_and (&) |
left |
| 8 | b_xor (^) |
left |
| 7 | b_or (|||) |
left |
| 6 | and (&&) |
left |
| 5 | xor |
left |
| 4 | or (||) |
left |
| 3 | ?? coalesce, ?: elvis |
right |
| 2 | ? : ternary, postfix if/unless/else |
right |
| 1 | => arrow, = assignment / destructuring |
right |
Power and unary minus. Power (level 15) binds tighter than unary minus (level
16 prefix), but the right operand of the right-associative ** re-enters at the
prefix level, and unary minus wraps the power by AST shape. One rule
(AST-driven precedence) governs both, so -1 ** 0 parses as -(1 ** 0) = -1 and
(-1) ** 2 = 1, entirely from the table.
The operator catalogue¶
- Logical
and(&&),or(||),xor,not(!): short-circuitand/or, booleanxor, prefixnot, all over the single truthiness rule. Word and symbol spellings are interchangeable; the result is aBool. - Bitwise
b_and,b_or,b_xor: integer-only; a non-int operand is a type error. - Equality
==,!=: typed equality.1 == "1"is false.===/!==are accepted aliases; raw reference identity is thesame(a, b)builtin. - Ordering
<><=>=<=>: total within the number tower and between two strings (byte-lexicographic). Cross-kind ordering is an error, never silent juggling. One comparator backs them all. - Membership
in/not in: for a collection, true iff some element is==the needle (typed, so"1" in [1]is false); for a string haystack, substring containment of the rendered needle. - Regex
matches: Go RE2 dialect. The right operand is a string expression whose contents are the RE2 pattern; there is no/re/literal token (a bare/is always division). Inline flags use RE2's(?flags)prefix. A backtick raw string is the ergonomic form for patterns with backslashes. - String predicates
starts with/ends with. - Quantifiers
has some/has every: a predicate over an iterable using arrows, e.g.xs has some (x => x > 0),xs has every (x => x.valid). - Range
..:1..5,'a'..'e'; the same engine asrange(...). - Arithmetic
+ - * / // % **: numeric only."3" + 4is a type error, not7./is true division (float unless exact),//is floor division,%is remainder,**is right-associative power.+is never array union (that is themergefilter). Integer overflow is a defined error, not silent promotion to float. - Concat
~: string concatenation, each operand rendered byToText. Kept distinct from+. - Ternary / elvis / coalesce: three distinct fallthrough predicates.
a ? b : c(truthiness ofa; no-elsea ? byields empty whenais falsy);a ?: b(aif truthy elseb, through a defined-safe path);a ?? b(aif defined and not null elseb, predicate is definedness). The coalescing operators anddefaultsuppress undefined-misses across the entire left-operand access chain, not just its final hop, souser.nick ?? "anon"yields"anon"whenuseritself is absent. The per-hop table is in Types. - Assignment / destructuring
=: expression-form assignment returning the assigned value, right-associative. Sequence destructuring[a, b] = e, mapping destructuring{name} = e, rename{key: alias} = e, elided slots[, b] = e. Over/under-supply is an error by default; explicit[a, b, ...rest] = ecaptures the tail and[a, b?] = emarks a slot optional. - Arrow
=>:x => expr,(x, y) => expr,() => expr. Closes over template scope; params shadow context for the call duration. Used bymap/filter/sort/reduce/findand the quantifier operators. Params may carry type annotations:(x: int) => x * 2. - Grouping
( ): overrides precedence and is the entry point for arrow param lists and parenthesized destructuring targets.
Primary expressions and call arguments¶
A primary is a bare identifier (context lookup), a literal, ( expr ), a
sequence or mapping literal, a function call name(args), or a postfix chain
(access, subscript, slice, call, filter, test). Attribute and index access
(a.b, a[k], a?.b, a?[k], slices) are specified in Types.
Literals cover numbers (42, 1_000_000, 3.14, 0xFF, 0b1010, 0o755,
1e9), bool/null (true, false, null, with none an alias for null),
sequence literals ([1, 2, 3], spread [...xs, 4]), and mapping literals
({a: 1, b: 2}, shorthand {a}, computed keys {(expr): v}, spread
{...base, c: 3}).
One argument grammar for every callable. Filter calls, function calls, and
test calls share a single argument grammar: positional e, named name: e, and
spread ...e, in that order, with declared defaults filling any parameter not
supplied. The short forms are special cases:
name(args): a function call.x | fandx | f(args): a filter call.x | fis the zero-explicit- argument case (the pipe supplies the first argument, defaults fill the rest);x | f(a, key: c, ...rest)isf(x, a, key: c, ...rest).x is t,x is t arg, andx is t(args): a test call.x is tis the zero-argument case;x is t argis the one-positional short form;x is t(args)admits named and spread arguments.
Because all three resolve to the same argument grammar and the same callable signature, named arguments are available uniformly across filters, functions, methods, and tests.
Next¶
- Control Flow:
if,for,set, and the region statements. - Standard Library: the built-in filters, functions, and tests.
- Types: the value model and the rules these operators obey.