Operators#
> Full precedence table, exact type rules, and the four anime operators V3 really implements.
Precedence#
Loosest binding first. Everything on one row is left-associative.
| Level | Operators |
|---|---|
| 1 | or |
| 2 | and |
| 3 | == != < > <= >= |
| 4 | + - NAKAMA |
| 5 | * / % |
| 6 (prefix) | not - PLUS ULTRA FINAL FORM TSUNDERE |
| 7 (postfix) | .field .method() [index] |> |
Parentheses group as expected.
-- Arithmetic, comparison and logic.
task main() -> void {
pilot a: int = 17
pilot b: int = 5
say word_from_int(a + b)
say word_from_int(a - b)
say word_from_int(a * b)
say word_from_int(a / b)
say word_from_int(a % b)
say word_from_bool(a > b)
say word_from_bool(a == 17)
say word_from_bool(a != b)
say word_from_bool(a <= 17)
say word_from_bool(a > b and b > 0)
say word_from_bool(a < b or b > 0)
say word_from_bool(not false)
-- Unary minus applies to an expression. A negative *literal*
-- is written as a subtraction: `0 - 5`.
pilot neg: int = 0 - 5
say word_from_int(-neg)
}
22
12
85
3
2
true
true
true
true
true
true
true
5
Type rules#
The checker is strict — these are the exact rules, not approximations.
| Operator | Accepts | Produces |
|---|---|---|
+ | both numeric (int/num), or both word | num if either side is num, else int; word for word+word |
- * / | both numeric | num if either side is num, else int |
% | **both int** | int |
NAKAMA | **both int** | int |
and or | **both bool** | bool |
== != | equality-compatible types | bool |
< > <= >= | both numeric | bool |
not | bool | bool |
- (prefix) | numeric | same as operand |
PLUS ULTRA FINAL FORM TSUNDERE | **int only** | int |
Two consequences worth internalising:
- **
%is integer-only.**7.5 % 2.0is a type error, not a float remainder. - **
and/ordo not coerce.** There is no truthiness.if count and flagis an error unlesscountis alreadybool.
+ is the only overloaded operator: numeric addition and word concatenation.
say "Muv" + "-" + "Luv"
say word_from_int(2 + 3)** (exponent) does not exist — you get expected ',', found '**'. Use math::pow(base, exp) for num, or std_pow(base, exp) for int.
Negative numbers#
There is no negative literal. -5 is unary minus applied to 5. In an initialiser position, write the subtraction the compiler's own source uses:
pilot below: int = 0 - 5
say word_from_int(-below)The anime operators#
Four of the bible's anime operators are implemented. Their V3 semantics are not what the specification describes — the specification defines emotional scaling formulas; V3 implements simple integer arithmetic.
-- The anime operators that V3 actually implements.
-- The three unary forms are PREFIX and accept `int` only.
task main() -> void {
pilot base: int = 6
say word_from_int(FINAL FORM base) -- base * base
say word_from_int(PLUS ULTRA base) -- base * 2
say word_from_int(TSUNDERE base) -- 0 - base
-- NAKAMA is infix, at the same precedence as + and -.
say word_from_int(3 NAKAMA 4)
}
36
12
-6
8
| Operator | Form | V3 lowering | Bible says |
|---|---|---|---|
FINAL FORM | prefix, int | x * x | postfix base FINAL FORM, plus a 5-second build pause |
PLUS ULTRA | prefix, int | x * 2 | infix base PLUS ULTRA emotion → base * (1 + e²) |
TSUNDERE | prefix, int | 0 - x | postfix; !x for bool, -x for num |
NAKAMA | infix, int | a + b | a + b + (a * b * 0.1) |
Position and type both differ from the specification. In V3 all three unary operators are prefix and accept **int only**. base FINAL FORM fails with expected ',', found 'FINAL FORM', and TSUNDERE true fails with operator 'TSUNDERE' does not accept bool.
Keyword matching is case-insensitive, so plus ultra, Plus Ultra and PLUS ULTRA all lex to the same token. Both words must be present — PLUS alone is an ordinary identifier.
The pipe operator#
|> binds at postfix precedence and rewrites a call so the left value becomes its first argument.
-- `|>` feeds the left value in as the FIRST argument of the call on the
-- right, so `x |> f(y)` is exactly `f(x, y)`.
task double(n: int) -> int { give back n * 2 }
task offset(n: int, by: int) -> int { give back n + by }
task main() -> void {
say word_from_int(5 |> double())
say word_from_int(5 |> offset(3))
say word_from_int(5 |> double() |> offset(1))
}
10
8
11
x |> f() is f(x)
x |> f(y) is f(x, y)
x |> f() |> g() is g(f(x))The right-hand side must be a bare task name followed by an optional argument list. You cannot pipe into a method call, a shape constructor, or a namespaced builtin like math::sqrt.
Postfix forms#
| Form | Meaning |
|---|---|
value.field | Shape field access |
value.method(args) | Instance method or builtin method call |
Shape::method(args) | Associated method call |
namespace::call(args) | Builtin namespace call (math::sqrt, fs::read, …) |
word[i] | Indexing — **only on word**, yielding a one-character word |
value |> task() | Pipe |
Indexing an array handle with [i] does not work. Arrays use array_get(handle, i) — see Arrays.
Operator overloading#
Writing impl Add for YourShape registers an implementation, and the method is callable by name. V3 does not rewrite the + token to dispatch to it — see Shapes & impl.
-- `impl Doctrine for Shape` registers an operator implementation.
-- V3 does not accept a `doctrine` *declaration*, but it does accept
-- this impl form for the built-in operator doctrines, and the method
-- is callable by name.
shape Vec2 {
x: int
y: int
}
impl Add for Vec2 {
task add(self, other: Vec2) -> Vec2 {
give back Vec2 { x: self.x + other.x, y: self.y + other.y }
}
}
impl Vec2 {
task show(self) -> word {
give back "({self.x}, {self.y})"
}
}
task main() -> void {
pilot a: Vec2 = Vec2 { x: 1, y: 2 }
pilot b: Vec2 = Vec2 { x: 10, y: 20 }
pilot c: Vec2 = a.add(b)
say c.show()
}
(11, 22)
Not implemented#
| Operator | Status |
|---|---|
** exponent | V4 — use math::pow / std_pow |
? error propagation | V4 |
or else fallback | V4 |
|| as an xm3 branch separator | V4 — lexes as one token but has no grammar |
as? downcast | V4 |
*ptr, .read(), .write(), .offset(), .cast<U>() | V4 |
.. ranges | V4 |