Skip to main content

6  Operators, Sections & Underscore

Edva uses Pratt parsing to resolve operator precedence and associativity. This chapter specifies all operator tiers, comparison and range semantics, operator and field-accessor sections, and the scoping rules of the implicit placeholder _ and cycle index _i.

6.1 Precedence and binding power table

Tier LBP RBP Operators Associativity
cycle 10 20 @ @@ >-- --> --^ left
sequence 20 30 ; left
apply 30 30 $ right
feed 40 50 $> $>> left
choice 50 60 | [pattern] !| |-> |+> |--> |++> left
assignment 60 50 = := += ::= right
range 70 80 .. left
logical or 80 90 || left
logical and 90 100 && left
bitwise or 100 105 .|. left
bitwise xor 105 110 .^. left
bitwise and 110 120 .&. left
comparison 120 130 < > == != !< !> left, same-line
shift 130 140 .<. .>. left
addition 140 150 + - left
multiplication 150 160 * / % left
unary prefix 160 180 ! + & ++ .!. ~~ ? ?? -x #size right
postfix / apply 170 180 call () . :: : cast {N} @[...] !! --! left
primary 180 180 primary left
Parsing Rule Behavior
Left-associative tiers (RBP > LBP) Operator binds tightly on the left (a - b - c \(\equiv\) (a - b) - c)
Right-associative tiers (RBP <= LBP) Operator extends rightward (f $ g $ x \(\equiv\) f(g(x)))
Choice absorption ($ / $>) $ (LBP 30) and $> (LBP 40) sit below choice (LBP 50), absorbing unparenthesized RHS choices (a $> b | c | d \(\equiv\) a $> (b | c | d))
Operator sections (op rhs) 13 binary operators with a leading operand omitted and mandatory trailing space form a 1-arg function over _ with RBP 130

6.2 Relational, equality, and chained comparisons

Comparisons sit at LBP 120, RBP 130 and return a bare flag <> (<+> or <->). Edva writes greater-or-equal as !< (“not less”) and less-or-equal as !> (“not greater”); <= and >= are not comparison operators (=> is the function arrow).

Operator Meaning Valid Operand Types Floating-Point / Special Semantics
< Less than Numeric, Rune, String, Addr Ordered (OLT); <-> if either operand is NaN. Rejected on rams/enums (E3127)
> Greater than Numeric, Rune, String, Addr Ordered (OGT); <-> if either operand is NaN. Rejected on rams/enums (E3127)
!< Greater than or equal (\(\ge\)) Numeric, Rune, String, Addr Ordered (OGE); <-> if either operand is NaN (distinct from !(a < b))
!> Less than or equal (\(\le\)) Numeric, Rune, String, Addr Ordered (OLE); <-> if either operand is NaN (distinct from !(a > b))
== Equal Scalars, String, records, enums, Type, two bare Flags <> Deep structural equality on records and enums; on two Flags, logical XNOR (E3027 on other rams, E3084 on fn payloads); zero-alloc segment check on str == (p1, p2, ...)
!= Not equal Scalars, String, records, enums, Type, two bare Flags <> Logical negation of == (UNE on floats: <+> if either operand is NaN); on two Flags, logical XOR

6.2.1 Chained comparisons

Relational operators (<, >, !<, !>) on the same source line chain mathematically, evaluating interior operands at most once:

Chained Expression Equivalent Short-Circuit Lowering Middle Operand Evaluation
a < x < b t = x; (a < t) && (t < b) Evaluated at most once
a !> x < b t = x; (a !> t) && (t < b) Evaluated at most once
a !> x !> b t = x; (a !> t) && (t !> b) Evaluated at most once
b > f(x) > a t = f(x); (b > t) && (t > a) f(x) called at most once
#use io

in_half_open: Int, Int, Int => <>
in_half_open = x, lo, hi => lo !> x < hi

io::out $ in_half_open(5, 0, 10)   // <+>
io::out $ in_half_open(10, 0, 10)  // <->

6.3 Range operators (.., ..., ..=)

Range operators sit at LBP 70, RBP 80. Endpoints must be adjacent to the operator without whitespace (E2025). Ranges are consumed by choice guards [...], cycles @, and slice/substring calls x(...):

Operator Interval Kind Math Notation Lower Bound a Upper Bound b Choice Guard [...] Cycle @ Slice / View x(...)
a..b Half-open (left-closed, right-open) \([a, b)\) Closed (\(a \le x\)) Open (\(x < b\)) a !> x < b a through b - 1 Elements [a, b)
a...b / a..=b Closed (both inclusive) \([a, b]\) Closed (\(a \le x\)) Closed (\(x \le b\)) a !> x !> b a through b Elements [a, b]
a.. Lower-bounded closed \([a, \infty)\) Closed (\(a \le x\)) Unbounded x !< a Unbounded from a From a to ?x (-k.. from ?x - k)
..b Upper-bounded open \((-\infty, b)\) Unbounded Open (\(x < b\)) x < b 0 through b - 1 Elements [0, b)
...b / ..=b Upper-bounded closed \((-\infty, b]\) Unbounded Closed (\(x \le b\)) x !> b 0 through b Elements [0, b]
.. Unbounded \((-\infty, \infty)\) Unbounded Unbounded <+> Unbounded from 0 Full view [0, ?x)

6.4 Arithmetic, bitwise, shift, logical, and map operators

Category Operators Operand Types Result Type & Semantics
Arithmetic + - * / % Numeric (Int, Float, i8..u64, f32, SIMD vectors) Same numeric type (+ also concatenates String, Rune, and Builder)
Bitwise .|. .^. .&. .!. Integerish (Int, i8..u64, integer SIMD vectors) Same integer type (.!. is unary bitwise NOT)
Bit shift .<. .>. Integerish Left shift (.<.) and right shift (.>.); sections infer Int => Int
Logical && || <> (Flag) or compatible heap ramifications Short-circuiting AND / OR
Map access m ^ k
m ^? k
m ^- k
Map K ^ V, key K ^: < V | Error > lookup (m ^ k = v inserts)
^?: <> key existence check
^-: <> key removal

6.5 Operator sections and field-accessor sections

An operator section writes a 1-argument function value => value op rhs by omitting the left operand of a supported binary operator. A field-accessor section writes a 1-argument projection r => r.field using a leading dot inside parentheses.

6.5.1 Section syntax and rules

Section Form Syntax Desugaring Rules & Diagnostics
Additive section (+ rhs), (- rhs) _ => _ + rhs, _ => _ - rhs Mandatory space after operator (-3 is negative literal; +b is Builder freeze)
Multiplicative section (* rhs), (/ rhs), (% rhs) _ => _ * rhs, _ => _ / rhs, _ => _ % rhs RHS binds with RBP 130 (* 2 + 3 \(\equiv\) _ => _ * (2 + 3))
Comparison section (< rhs), (> rhs), (== rhs) _ => _ < rhs, _ => _ > rhs, _ => _ == rhs Missing space (>3) emits E2031. !=, !<, !> do not form sections; use (_ != rhs)
Shift section (.<. rhs), (.>. rhs) _ => _ .<. rhs, _ => _ .>. rhs Automatically infers Int => Int when unannotated
Bitwise section (.&. rhs), (.^. rhs), (.|. rhs) _ => _ .&. rhs, etc. Since bitwise LBP < 130, parenthesize bitwise RHS inside arithmetic sections (+ (3 .&. 1))
Excluded operators ||, &&, $, $>, $>>, !=, !<, !> — Do not form sections; write (_ != 0) or x => ...
Field-accessor section (.field)
(.0)
(.outer.inner)
r => r.field
r => r.0
r => r.outer.inner
Must be parenthesized ((.) or bare .field emits E2064). Uses a fresh internal parameter, so it never shadows an enclosing _
#use io
#use fp

#type User (name: String, age: Int)

double = * 2
shl1 = .<. 1
get_age: User => Int
get_age = (.age)

xs = Int{}
xs += 3
xs += 5
mapped = fp::map(double, xs)

io::out $ shl1(mapped(0)!!)                 // 12
io::out $ get_age(User("Alice", 30))        // 30
f = >3
x = (.)

6.5.2 Section evaluation by syntactic context

Context Example Behavior
Direct binding / call / pipeline f = * 2; 5 $> (* 2) $> (+ 1) Evaluates RHS each time the section is called
Choice predicate guard x [< 0] "neg" [> 0] "pos" | "zero" Applies predicate section to scrutinee x
Bare ram payload branch opt | (+ 2) | 0 / opt | (.name) | "" Applies section directly to the unwrapped slot payload
Enum payload branch box [Item] (+ 2) | 0 Applies section directly to variant payload (E3084 on payload-free variant)
Ordinary value/predicate branch mode [> 0] (* 2) | (+ 1) Returns the section as a function value (Int => Int) without applying to mode
Ram bind ($>>) opt $>> (x => <+ x + 1 +>) Continuation must return a ramification; opt $>> (+ 1) emits E3091
#type Option < Int | >
some = Option(<+ 7 +>)
res = some $>> (+ 2)

6.6 Implicit underscore (_) and cycle index (_i)

The exact identifier _ has context-dependent implicit roles, governed by strict static scoping rules:

Context Syntax Meaning of _ / _i Diagnostic on Misuse
Grouped implicit lambda (_ * 2), (10 - _), ((a = _, b = 1)) Innermost enclosing (...) group containing a free _ becomes a 1-arg function (_ repeated inside the same group refers to the same single parameter) —
Operator section (* 2), (* _) Implicit left operand (* _ \(\equiv\) (_ * _)) E2031 if space omitted
Headless choice / guard [< 0] -1 | 1, ['a'...'z'] Wraps choice/guard into a 1-arg function over _ (outside statement @ body) E3003 if omitted subject has no _
Implicit cycle body 10..12 @ io::out(_ + _i) _ is current element/byte/map-value; _i is 0-based index (or map key K in m @). Saved/restored across nested cycles; does not survive cycle exit E3004: reading _ or _i after cycle exit
Ram payload branch r | _ + 1 | 0 _ is the selected slot’s payload (E4011 fatal abort if _ extracts negative Error slot on failure) E3004: _ on unit/absence slot (<Int|> 2nd branch)
Enum payload branch e [Item] _ + 1 | 0 _ is the matched variant’s payload E3004: _ on unit variant ([Empty] _)
Ordinary guarded branch x [> 0] x + 1 | 0 Does not bind _ in the branch body (preserves function-valued branches) E3004 on 7 [> 0] _ + 1 | 0
Structured pattern wildcard p [(a = _, b = 20)] ... Matches component without binding a variable ([Item _] is rejected) E2109: redundant _ in [Item _]
Explicit parameter _ => expr / a, _ => expr Forbidden across all lambdas, functions, cycles, and #! params E2040: explicit _ parameter
Assignment target _ = expr / _ := expr Forbidden E2044: cannot assign to _
Unbound value reference x = _ Forbidden outside an active implicit binding scope E3004: Undefined: _
#use io

square = (_ * _)
sub10 = (10 - _)
io::out $ square(4) + sub10(3)   // 23

#type Scores String ^ Int
m = Scores()
m ^ "alice" = 95
m @
   io::out $ (_i, _)                // ("alice", 95)
_ = 7
f = _ => 42
x: Int
x = _