Operator Rules

Formal typing and evaluation rules for Ori operators.

Legend

NOTATION
────────
T, U, E         type variables
v, v1, v2       values
e, e1, e2       expressions
env             type environment
|-              "entails" (type judgment)
->              type-level transformation
=>              evaluation (reduces to)
─────           inference rule separator (premises above, conclusion below)
[cond]          side condition
x               type product (left x right)

TYPE RULES
──────────
    premise1    premise2
    ────────────────────    RULE-NAME
         conclusion

READ AS: "if premise1 and premise2 hold, then conclusion holds"

EVALUATION RULES
────────────────
pattern => result [condition]

READ AS: "pattern evaluates to result when condition holds"

ASSOCIATIVITY
─────────────
assoc=left      left-to-right grouping: a op b op c = (a op b) op c
assoc=right     right-to-left grouping: a op b op c = a op (b op c)

MAINTENANCE
───────────
- Add new operators: copy existing block, modify rules
- Modify behavior: update relevant => rules
- Type changes: update -> rules and inference rules
- Keep in sync with: grammar.ebnf, ori_types/src/infer/expr/operators.rs, ori_eval/src/interpreter/mod.rs

Pipe |>

assoc=left
prec=16 (lowest binary)

TYPE RULES
──────────
    e1 : T    f : (P: T, ...) -> U    [P is single unspecified param of f]
    ─────────────────────────────────────────────────────────────────────    PIPE-FILL
                             e1 |> f(...) : U

    e1 : T    method : (self: T, ...) -> U
    ──────────────────────────────────────    PIPE-METHOD
              e1 |> .method(...) : U

    e1 : T    g : (T) -> U
    ────────────────────────    PIPE-LAMBDA
         e1 |> g : U

UNSPECIFIED PARAMETER
─────────────────────
A parameter is "unspecified" when:
  (a) not provided in the call arguments, AND
  (b) has no default value

Parameters with defaults are treated as filled for pipe resolution.

COMPILE ERRORS
──────────────
Zero unspecified params  => "all parameters already specified; nothing for pipe to fill"
2+ unspecified params    => "ambiguous pipe target; specify all parameters except one"

DESUGARING
──────────
e1 |> f(a: v)            => { let $__pipe = e1; f(<unspecified>: __pipe, a: v) }
e1 |> .method(a: v)      => { let $__pipe = e1; __pipe.method(a: v) }
e1 |> (x -> expr)        => { let $__pipe = e1; (x -> expr)(__pipe) }
e1 |> f(a: v)?           => { let $__pipe = e1; f(<unspecified>: __pipe, a: v)? }

LEFT-TO-RIGHT: a |> f |> g |> h = h(g(f(a)))

Coalesce ??

assoc=right
prec=15

TYPE RULES
──────────
e1 : Option<T>    e2 : T
────────────────────────    COALESCE-UNWRAP
      e1 ?? e2 : T

e1 : Option<T>    e2 : Option<T>
────────────────────────────────    COALESCE-CHAIN
      e1 ?? e2 : Option<T>

e1 : Result<T,E>    e2 : T
──────────────────────────    COALESCE-RESULT-UNWRAP
      e1 ?? e2 : T

e1 : Result<T,E>    e2 : Result<T,E>
────────────────────────────────────    COALESCE-RESULT-CHAIN
      e1 ?? e2 : Result<T,E>

e1 : Never    e2 : T    [Never unifies with Option<T>]
────────────────────────────────────────────────────    COALESCE-NEVER-LEFT
                  e1 ?? e2 : T

e1 : Option<T>    e2 : Never
────────────────────────────    COALESCE-NEVER-RIGHT
      e1 ?? e2 : T

EVALUATION
──────────
Some(v) ?? e2 => v         [type(e1) != type(e1 ?? e2)]
Some(v) ?? e2 => Some(v)   [type(e1) = type(e1 ?? e2)]
None ?? e2 => eval(e2)
Ok(v) ?? e2 => v           [type(e1) != type(e1 ?? e2)]
Ok(v) ?? e2 => Ok(v)       [type(e1) = type(e1 ?? e2)]
Err(_) ?? e2 => eval(e2)

SHORT-CIRCUIT: e2 not evaluated when e1 is Some/Ok

Arithmetic + - * / % div

assoc=left
prec=4 (* / % div @), prec=5 (+ -)

TYPE RULES
──────────
e1 : int    e2 : int
────────────────────    ARITH-INT
    e1 op e2 : int

e1 : float    e2 : float
────────────────────────    ARITH-FLOAT
     e1 op e2 : float

e1 : str    e2 : str
────────────────────    CONCAT
    e1 + e2 : str

e1 : Duration    e2 : Duration
──────────────────────────────    DURATION-ADD-SUB
     e1 +|- e2 : Duration

e1 : Duration    e2 : int
─────────────────────────    DURATION-MUL-DIV
    e1 *|/ e2 : Duration

e1 : int    e2 : Duration
─────────────────────────    DURATION-MUL-REV
     e1 * e2 : Duration

e1 : Duration    e2 : Duration
──────────────────────────────    DURATION-MOD
      e1 % e2 : Duration

e1 : Size    e2 : Size
──────────────────────    SIZE-ADD-SUB
    e1 +|- e2 : Size

e1 : Size    e2 : int
─────────────────────    SIZE-MUL-DIV
    e1 *|/ e2 : Size

e1 : int    e2 : Size
─────────────────────    SIZE-MUL-REV
     e1 * e2 : Size

e1 : Size    e2 : Size
──────────────────────    SIZE-MOD
     e1 % e2 : Size

EVALUATION
──────────
n1 + n2 => sum           [overflow -> panic]
n1 - n2 => diff          [overflow -> panic]
n1 * n2 => product       [overflow -> panic]
n1 / n2 => quotient      [n2 = 0 -> panic, truncates toward zero]
n1 % n2 => remainder     [n2 = 0 -> panic]
n1 div n2 => floor_quot  [n2 = 0 -> panic, floor toward -inf]
s1 + s2 => concat

Power **

assoc=right
prec=2 (tighter than unary, looser than postfix)

TYPE RULES
──────────
e1 : int    e2 : int
────────────────────    POW-INT
    e1 ** e2 : int

e1 : float    e2 : float
──────────────────────────    POW-FLOAT
     e1 ** e2 : float

e1 : float    e2 : int
─────────────────────────    POW-FLOAT-INT
     e1 ** e2 : float

e1 : int    e2 : float
─────────────────────────    POW-INT-FLOAT
     e1 ** e2 : float

EVALUATION
──────────
n1 ** n2 => int_power    [n1 : int, n2 : int, n2 >= 0]
n1 ** n2 => panic        [n1 : int, n2 : int, n2 < 0, "negative exponent on integer"]
n1 ** 0 => 1             [for all n1, including 0 ** 0]
f1 ** f2 => libm_pow     [delegates to libm pow()]

OVERFLOW
────────
int ** int follows standard overflow behavior (panic in debug)

TRAIT DISPATCH
──────────────
** -> Pow -> power(self, rhs:)

Comparison == != < <= > >=

assoc=left
prec=8 (< <= > >=), prec=9 (== !=)

TYPE RULES
──────────
e1 : T    e2 : T    T : Eq
──────────────────────────    EQ
     e1 ==|!= e2 : bool

e1 : T    e2 : T    T : Comparable
──────────────────────────────────    ORD
      e1 <|<=|>|>= e2 : bool

EVALUATION
──────────
v == v => true
v1 == v2 => false        [v1 != v2]
v != v => false
v1 != v2 => true         [v1 != v2]
v1 < v2 => compare(v1, v2) = Less
v1 <= v2 => compare(v1, v2) != Greater
v1 > v2 => compare(v1, v2) = Greater
v1 >= v2 => compare(v1, v2) != Less

Logical && ||

assoc=left
prec=13 (&&), prec=14 (||)

TYPE RULES
──────────
e1 : bool    e2 : bool
──────────────────────    AND
     e1 && e2 : bool

e1 : bool    e2 : bool
──────────────────────    OR
     e1 || e2 : bool

EVALUATION
──────────
false && e2 => false     [e2 not evaluated]
true && e2 => eval(e2)
true || e2 => true       [e2 not evaluated]
false || e2 => eval(e2)

Bitwise & | ^ << >>

assoc=left
prec=10 (&), prec=11 (^), prec=12 (|), prec=6 (<< >>)

TYPE RULES
──────────
e1 : int    e2 : int
────────────────────────    BITWISE-INT
  e1 &|^|<<|>> e2 : int

e1 : byte    e2 : byte
──────────────────────────    BITWISE-BYTE
    e1 &|^ e2 : byte

e1 : byte    e2 : int
─────────────────────────    SHIFT-BYTE
   e1 <<|>> e2 : byte

EVALUATION
──────────
n1 & n2 => bitwise_and
n1 | n2 => bitwise_or
n1 ^ n2 => bitwise_xor
n1 << n2 => shift_left   [n2 < 0 -> panic, n2 >= width -> panic]
n1 >> n2 => shift_right  [n2 < 0 -> panic, n2 >= width -> panic]

CONSTRAINTS
───────────
int width = 64 bits (valid shift: 0..63)
byte width = 8 bits (valid shift: 0..7)

Range .. ..=

assoc=left
prec=7

TYPE RULES
──────────
e1 : int    e2 : int
────────────────────────    RANGE
   e1 ..|..= e2 : Range<int>

e1 : int
────────────────────    RANGE-OPEN
  e1 .. : Range<int>

e1 : int    e2 : int    e3 : int
────────────────────────────────    RANGE-STEP
   e1 ..|..= e2 by e3 : Range<int>

Unary - ! ~

prec=3 (between power and multiplicative)

TYPE RULES
──────────
e : int
───────────    NEG-INT
  -e : int

e : float
─────────────    NEG-FLOAT
  -e : float

e : Duration
────────────────    NEG-DURATION
  -e : Duration

e : bool
────────────    NOT
  !e : bool

e : int
───────────    BITNOT-INT
  ~e : int

e : byte
────────────    BITNOT-BYTE
  ~e : byte

CONSTRAINTS
───────────
-Size -> compile error (Size cannot be negative)

Type Conversion as as?

prec=1 (postfix)

TYPE RULES
──────────
e : T    T converts to U (infallible)
──────────────────────────────────────    AS-INFALLIBLE
            e as U : U

e : T    T converts to U (fallible)
────────────────────────────────────    AS-FALLIBLE
         e as? U : Option<U>

INFALLIBLE CONVERSIONS
──────────────────────
int -> float
byte -> int
char -> int

FALLIBLE CONVERSIONS
────────────────────
str -> int      (parse)
str -> float    (parse)
int -> byte     (range check)
float -> int    (truncate, range check)

Try ?

prec=1 (postfix)

TYPE RULES
──────────
e : Option<T>    enclosing returns Option<U>
────────────────────────────────────────────    TRY-OPTION
                   e? : T

e : Result<T,E>    enclosing returns Result<U,E>
────────────────────────────────────────────────    TRY-RESULT
                    e? : T

EVALUATION
──────────
Some(v)? => v
None? => return None
Ok(v)? => v
Err(e)? => return Err(e)

Never

UNIFICATION
───────────
unify(Never, T) = Ok    for all T
unify(T, Never) = Ok    for all T

COERCION
────────
e : Never
─────────    NEVER-COERCE
  e : T      for all T

SOURCES
───────
panic(msg:) : Never
todo() : Never
todo(reason:) : Never
unreachable() : Never
unreachable(reason:) : Never
break : Never               [in loop]
continue : Never            [in loop]
loop { e } : Never          [no break in e]
e? : Never                  [early return path when e is None/Err]

Precedence Table

PREC  OPERATORS              ASSOC   DESCRIPTION
────  ─────────              ─────   ───────────
1     . [] () ? as as?       left    postfix
2     **                     right   power
3     ! - ~                  right   unary
4     * / % div @            left    multiplicative
5     + -                    left    additive
6     << >>                  left    shift
7     .. ..= [by]            left    range (by is step modifier)
8     < > <= >=              left    comparison
9     == !=                  left    equality
10    &                      left    bitwise and
11    ^                      left    bitwise xor
12    |                      left    bitwise or
13    &&                     left    logical and
14    ||                     left    logical or
15    ??                     RIGHT   coalesce

Trait Dispatch

OPERATOR -> TRAIT -> METHOD
───────────────────────────
+    -> Add      -> add(self, other:)
-    -> Sub      -> subtract(self, other:)
*    -> Mul      -> multiply(self, other:)
/    -> Div      -> divide(self, other:)
div  -> FloorDiv -> floor_divide(self, other:)
%    -> Rem      -> remainder(self, other:)
**   -> Pow      -> power(self, rhs:)
@    -> MatMul   -> matrix_multiply(self, rhs:)
-    -> Neg      -> negate(self)
!    -> Not      -> not(self)
~    -> BitNot   -> bit_not(self)
&    -> BitAnd   -> bit_and(self, other:)
|    -> BitOr    -> bit_or(self, other:)
^    -> BitXor   -> bit_xor(self, other:)
<<   -> Shl      -> shift_left(self, rhs:)
>>   -> Shr      -> shift_right(self, rhs:)
==   -> Eq       -> equals(self, other:)
<    -> Comparable -> compare(self, other:)

RESOLUTION ORDER
────────────────
1. Primitive type -> direct evaluation
2. User type -> trait method lookup

Compound Assignment

DESUGARING
──────────
x op= y  =>  x = x op y    [parser-level rewrite]

SUPPORTED OPERATORS (TRAIT-BASED)
─────────────────────────────────
+=   desugars via  Add
-=   desugars via  Sub
*=   desugars via  Mul
/=   desugars via  Div
%=   desugars via  Rem
**=  desugars via  Pow
@=   desugars via  MatMul
&=   desugars via  BitAnd
|=   desugars via  BitOr
^=   desugars via  BitXor
<<=  desugars via  Shl
>>=  desugars via  Shr

SUPPORTED OPERATORS (LOGICAL)
─────────────────────────────
&&=  desugars to  x = x && y    [bool-only, short-circuit preserved]
||=  desugars to  x = x || y    [bool-only, short-circuit preserved]

CONSTRAINTS
───────────
- Left-hand side shall be a mutable binding (no `$` prefix)
- Compound assignment is a statement, not an expression
- Target expression is duplicated in AST (pure: no side effects)