Skip to main content

Operators

Arithmetic​

OperatorNameExampleResult
+Addition3 + 47
-Subtraction10 - 37
*Multiplication3 * 412
/Division10 / 42
%Remainder10 % 31
-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​

OperatorNameExampleResult
&Bitwise AND6 & 32
|Bitwise OR5 | 27
^Bitwise XOR7 ^ 34
~Bitwise NOT (unary)~0-1
<<Left shift1 << 416
>>Right shift64 >> 216

Logical​

OperatorNameExampleResult
&& or andLogical AND (short-circuit)true && falsefalse
|| or orLogical OR (short-circuit)false || truetrue
! or notLogical NOT (unary)!truefalse

&& 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​

OperatorNameExampleResult
==Equal3 == 3true
!=Not equal3 != 4true
<Less than1 < 2true
>Greater than2 > 1true
<=Less than or equal2 <= 2true
>=Greater than or equal3 >= 2true

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​

OperatorNameExampleResult
inContains2 in [1, 2]true
not inDoes not contain3 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​

OperatorNameDescription
|>PipePass 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:

StageMeaning
A functionApplied to the value, which becomes its first argument
A type nameOn 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 valueConstrains 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, and str are also callable constructors. Without this rule, ?p |> int would stop constraining the model and merely try to convert whatever came back.
  • A struct you 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 |> City would have become City(?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
note

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:

  1. Unary: ~, !, -
  2. Multiplicative: *, /, %
  3. Additive: +, -
  4. Shifts: <<, >>
  5. Bitwise AND: &
  6. Bitwise XOR: ^
  7. Bitwise OR: |
  8. Comparison and membership: ==, !=, <, >, <=, >=, in, not in
  9. Logical AND: && / and
  10. Logical OR: || / or
  11. 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 int range
  • 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