Operators and casts
Prismio 0.1 arithmetic, comparison, logical, bitwise, shift, unary, assignment, and cast operators.
Last verified
Operators form expressions from typed operands. Prismio provides arithmetic + - * / %, comparison == != < <= > >=, short-circuit logic and or, none-coalescing ??, bitwise & | ^, shifts << >>, and unary ! - ~. Explicit casts use expression as Type. String has its own set — see String operators below.
Operators are statically checked. Prismio generally requires matching operand types and does not insert C-style integer promotions or integer-to-float conversions.
Arithmetic
+, -, *, /, and % operate on compatible numeric values. On integers % takes the dividend's sign (-7 % 2 is -1; floorMod takes the divisor's). On Float it is C's fmod: -7.5 % 2.0 is -1.5, and x % 0.0 is NaN. Unary - negates a signed integer or floating-point value, and on a Float it flips only the sign, so -(0.0) is -0.0.
Float comparisons follow IEEE 754: NaN != NaN is true, and ==, <, <=, >, >= with a NaN operand are false.
fn main() -> Int {
let width: Int = 7
let height: Int = 6
let area = width * height
let remainder = area % 5
return area + remainder - 44
}Division or remainder by zero, signed overflow, and the most negative signed value divided by -1 are not recoverable Prismio exceptions. Avoid those inputs; the exact backend outcome is not a portable program contract.
Comparisons
== and != compare compatible scalar values, fieldless enums, and supported optional/none cases. <, <=, >, and >= order compatible numeric values. Comparisons return Bool.
fn main() -> Int {
let small: U8 = 4
let normal: Int = 10
if (small < normal) { return 0 }
return 1
}The two typed operands do not gain an implicit common width. Give the comparison one exact type, for example by widening small as Int.
Logical operators
and, or, and ! operate on Bool. and evaluates its right operand only when the left operand is true. or evaluates its right operand only when the left operand is false.
if (index >= 0 and index < limit) {
println(index)
}Prismio uses the words and and or, not && and ||. not is a second spelling of !, and is and is not of == and !=: if x is not none and not done { }. They are read into the same nodes as the symbols, is ranks with == and not binds as ! does, to the next operand only (not x == y negates x; write not (x == y) for the comparison). is sits between its operands as in Python, so write x is 3, never x is(3): a ( touching the word is an error, and a grouped operand takes a space (x is (y + 2)).
None-coalescing
a ?? b is a when the optional a has a value, and b when it is none. b is evaluated only in that case.
import std.io
import std.option
fn main() {
let found: Int? = none
let other: Int? = 4
println(found ?? 10) // 10
println(found ?? other ?? 10) // 4
println(other ?? 10 + 1) // 4: `+` binds tighter than `??`
}The result is a T when b is a T, and a T? when b is one. ?? needs import std.option, because it is a call to optionCoalesce in that module, and its left side must be an optional: 5 ?? 6 is refused.
Bitwise operators and shifts
&, |, ^, and unary ~ operate on compatible integers. << and >> shift an integer value. Right shift is arithmetic for signed types and logical for unsigned types.
Keep shift counts within the bit width of the left operand. Negative or oversized counts do not have a stable source-level safety guarantee.
let permissions: U8 = 5
let readable = (permissions & (1 as U8)) != (0 as U8)Assignment
Assignment forms are =, +=, -=, *=, /=, %=, &=, |=, and ^=. Direct and compound assignment to a binding require mut.
fn main() -> Int {
let mut bits: U8 = 1
bits |= 4 as U8
bits ^= 1 as U8
return (bits as Int) - 4
}Compound assignment is supported for plain variable bindings. It is not currently generalized to struct fields or indexed collection places. Assignment is a statement operation, not a value expression to embed inside a condition.
Precedence
From lowest to highest, binary precedence is:
orand== !=< <= > >=??|^&<< >>+ -* / %
Unlike C, Prismio deliberately binds bitwise operators more tightly than equality, so flags & mask != 0 groups as (flags & mask) != 0. Right shift is arithmetic for signed values and logical for unsigned values.
Unary operators and casts bind more tightly than the binary levels above, and a unary operator binds inside a cast: -x as T is (-x) as T. Parentheses are recommended whenever a mixed bitwise, comparison, and logical expression would otherwise require the reader to recall the complete table.
Operators at the same precedence normally group left-to-right; ?? is the exception and groups right-to-left, so a ?? b ?? c tries a, then b, then c. Do not use associativity to hide numerically significant grouping, especially for subtraction, division, shifts, or floating-point expressions.
Casts
Operands generally require exact compatible types. There are no implicit integer-width or integer/float promotions; cast explicitly.
let source: I16 = -12
let wide: I64 = source as I64
let magnitude: Float = (wide as Float) * -1.0The documented conversions are:
- widening a signed integer sign-extends;
- widening an unsigned integer,
Bool, orCharzero-extends; - narrowing an integer keeps the low-order bits;
- integer-to-float converts numerically but may lose precision;
- float-to-integer truncates toward zero and saturates: out-of-range values clamp to the destination's
MAX/MIN, and NaN becomes 0; x as Stringis the textx.toString()gives, for every number,Bool,CharandString;- an enum converts to its ordinal with
as Int, and nothing converts toBool.
as T states intent; it does not validate a range. When the value may not fit, write as T?: it is none rather than a truncated or saturated number -- 300 as U8?, -1 as U64?, 3.5 as Int?, and from text "42" as Int?. See conversions for the full rules.
A prefix operator binds inside a cast: -x as T is (-x) as T, so -1 as U8 is 255.
String operators
String supports five operators. Each is rewritten during semantic analysis into
the std.string call it means, so all of them need import std.string, and two of
them allocate exactly as the call they stand for does.
| Operator | Means | Allocates |
|---|---|---|
a == b, a != b | content equality, same answer as a.equals(b) | no |
a < b, a <= b, a > b, a >= b | sign of a.compare(b) — byte order | no |
a + b | a.concat(b) | yes |
s[i] | s.charAt(i) — the byte at i, NUL out of range | no |
s[start..<end] | s.slice(start, end) — stops before end, clamped | yes |
s[start..end] | s.slice(start, end + 1) — includes end, clamped | yes |
import std.io
import std.string
fn main() -> Int {
let name = "prismio"
if (name == "prismio") { println("matched") }
if (name < "rust") { println("sorts first") }
let head = name[0..<4]
let label = "lang: " + head
println(label)
return 0
}A String is a pointer, so a raw address comparison would quietly answer
"not equal" for two equal strings. == is the content comparison, and there is no
way to spell the address one.
+ and [a..b] produce an owned value, released like any call's result:
println(a + b) frees the joined string once println returns.
A chain of + is one call, not a nest of them: a + b + c becomes
a.concat(b, c), one allocation rather than one per +. Up to six parts are
supported; past that, build a Vec<String> and call join on it.
Both sides of + must be Strings. Nothing is promoted implicitly, so write
"n = " + count.toString().
No user-defined operator overloading
Prismio 0.1 has traits and impl blocks,
but a trait cannot give a type an operator: the String operators above are compiler
desugarings for one built-in type, not an overloadable protocol. Structs and lists
do not acquire arithmetic or comparison behavior merely because their fields or
elements support it. Write a named function for domain-specific operations.