Operators
Arithmetic
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 3 + 4 | 7 |
- | Subtraction | 10 - 3 | 7 |
* | Multiplication | 3 * 4 | 12 |
/ | Division | 10 / 4 | 2 |
% | Remainder | 10 % 3 | 1 |
- | Unary negation | -5 | -5 |
Two ints divide to an int, truncated toward zero, so 10 / 4 is 2 and -7 / 2 is -3. If either side is a float, the result is a float, so 10 / 4.0 is 2.5.
% takes its sign from the left operand, the same as C. So -7 % 2 is -1 and 7 % -2 is 1.
Integer arithmetic is checked. A result outside the 63-bit int range raises integer overflow rather than wrapping around.
Bitwise
| Operator | Name | Example | Result |
|---|---|---|---|
& | Bitwise AND | 6 & 3 | 2 |
| | Bitwise OR | 5 | 2 | 7 |
^ | Bitwise XOR | 7 ^ 3 | 4 |
~ | Bitwise NOT (unary) | ~0 | -1 |
<< | Left shift | 1 << 4 | 16 |
>> | Right shift | 64 >> 2 | 16 |
Logical
| Operator | Name | Example | Result |
|---|---|---|---|
&& or and | Logical AND (short-circuit) | true && false | false |
|| or or | Logical OR (short-circuit) | false || true | true |
! or not | Logical NOT (unary) | !true | false |
&& and || short-circuit. If the left operand already decides the answer, the right operand is never evaluated. Both operands must be bool, and mixing types is a type error.
The word forms are exact aliases of the symbols, not separate operators. a and b and a && b compile to the same thing and bind at the same level. Pick one style and stay with it.
Comparison
| Operator | Name | Example | Result |
|---|---|---|---|
== | Equal | 3 == 3 | true |
!= | Not equal | 3 != 4 | true |
< | Less than | 1 < 2 | true |
> | Greater than | 2 > 1 | true |
<= | Less than or equal | 2 <= 2 | true |
>= | Greater than or equal | 3 >= 2 | true |
Equality, meaning == and !=, requires both operands to be the same type. Mixing int and float is a type error that jade check catches. The one exception is char against str, explained in Types.
Ordering, meaning <, >, <=, and >=, works on five combinations: two ints, two floats, an int against a float, two bools where false sorts below true, and two strings compared character by character. Anything else is a type error.
Comparisons do not chain. 1 < 2 < 3 groups as (1 < 2) < 3, which compares a bool to an int and fails. Write 1 < 2 && 2 < 3 instead.
Membership
| Operator | Name | Example | Result |
|---|---|---|---|
in | Contains | 2 in [1, 2] | true |
not in | Does not contain | 3 not in [1, 2] | true |
in works on three kinds of value. On an array it asks whether any element equals the value. On a string it asks whether the text is a substring. On a dict it asks whether the text is a key, never a value. It always produces a bool and never raises on a type mismatch, because an element of another type simply answers false.
print(2 in [1, 2, 3]) // true
print("ell" in "hello") // true
print("name" in {"name": 1}) // true
print("x" not in {"name": 1}) // true
Pipe
| Operator | Name | Description |
|---|---|---|
|> | Pipe | Pass the left-hand value as the first argument to the right-hand function |
The pipe operator passes a value through a chain of function calls, from left to right. x |> f means the same as f(x). When the right side is already a call with arguments, the piped value goes in as the first argument, so 5 |> add(3) means add(5, 3).
fn double(x) { return x * 2 }
fn add(a, b) { return a + b }
// Simple pipe
let n = 5 |> double // 10
// Chained pipes, left-associative
let m = 3 |> double |> double // 12
// Pipe with extra arguments; the value goes in first
let r = 5 |> add(3) // add(5, 3) = 8
// Pipe to print
"hello" |> print
// Pipe with arithmetic on the left
let x = (2 + 3) |> double // 10
What a stage can be
A stage is whatever sits to the right of a |>. There are three kinds. Which one applies depends on what the name refers to, not on where the |> appears:
| Stage | Meaning |
|---|---|
| A function | Applied to the value, which becomes its first argument |
| A type name | On a prompt dereference, it constrains generation with a grammar and coerces the reply. Anywhere else it is the ordinary type constructor, so x |> int means int(x) |
A Grammar value | Constrains sampling on a prompt dereference |
Two rules settle a name that could mean more than one thing. They only matter on a prompt dereference:
- A builtin type keyword is always a type, never a function.
int,float,bool,char, andstrare also callable constructors. Without this rule,?p |> intwould stop constraining the model and merely try to convert whatever came back. - A
structyou declared is always a type. A struct registers a constructor under its own name, so judged on callability alone, every struct looks like a function. Without this rule,?p |> Citywould have becomeCity(?p).
Everything else prefers a function.
prompt p = "What is 21 + 21? Respond with only the number."
let n = ?p |> int |> double // constrain, coerce, then apply
let g = Grammar.new('"yes" | "no"')
let a = ?p |> g // constrain sampling with a grammar
Until v1.2.0, this was really two operators sharing one spelling. A |> after a prompt dereference went through a different parse rule, which accepted exactly one constraint and could not chain, and a typed dereference was banned inside print(...). Both restrictions are gone. A stage that is none of the three kinds above now produces an InvalidPipeStage type error naming what it found, rather than a parse error talking about tokens.
Pipes are left-associative and bind more loosely than every other operator. So the whole expression to the left of |> is evaluated first, and the result is what gets passed to the function on the right.
Precedence
Operators bind from tightest to loosest in this order:
- Unary:
~,!,- - Multiplicative:
*,/,% - Additive:
+,- - Shifts:
<<,>> - Bitwise AND:
& - Bitwise XOR:
^ - Bitwise OR:
| - Comparison and membership:
==,!=,<,>,<=,>=,in,not in - Logical AND:
&&/and - Logical OR:
||/or - Pipe:
|>, the loosest of all. The entire left expression becomes the piped value.
Note that comparison binds more loosely than the bitwise operators, which is the opposite of C. So 1 << 2 & 15 groups as (1 << 2) & 15, and a & b == c groups as (a & b) == c.
let x = 2 + 3 * 4
let y = 1 << 2 & 15
let z = 1 < 2 && 3 > 0
When an operator fails
Operator failures divide into two groups by when Jade finds them. The split matters, because only one group can be caught with try.
Caught by jade check, before anything runs. These are type errors. The program never starts, so it produces no output at all.
- Mismatched operands:
1 + true,1.0 & 2 - Cross-type equality:
1 == 1.0 - Non-bool logic:
1 && true - A pipe stage that cannot be applied:
5 |> 3
Raised while the program runs. These depend on values rather than types, so the checker cannot see them coming. You can catch each one with try and catch.
- Division by zero:
x / 0 - Remainder by zero:
x % 0 - Invalid shift amount, negative or ≥ 64:
1 << 64 - Integer overflow: a result outside the 63-bit
intrange - Index out of bounds:
[1, 2][5] - A missing dict key:
d["nope"]
An operator on a value whose type the checker could not work out also lands in the second group. An element of a mixed array and a value from an imported package are both examples. The check is delayed until the program runs, not skipped:
let mixed = [1, "two"]
print(mixed[0] + mixed[1]) // passes `jade check`, then raises