Expressions and Operators
Klyn expressions are compact but strongly typed. This page covers arithmetic, comparisons, boolean logic, identity checks, membership, indexing, interpolation, and operator overloads.
Arithmetic and Updates
total = 10 + 5
delta = total - 3
scaled = delta * 2
ratio = scaled / 4 # Double: integer / integer is true division
quotient = scaled // 4 # Int: integer division
rest = 17 % 5
power = 2 ** 10
total += 1
total -= 1
total *= 2
total //= 2
total++
total--
The / operator performs true division. Dividing integer primitives therefore
produces a Double. The // operator performs floor division:
its quotient is rounded towards minus infinity, not towards zero. Integer operands keep
their integer family. When a floating-point operand is present, the result stays in the
promoted floating-point family (Float16, Float or Double);
operands are never first converted to integers. Both forms support compound assignment
(/= and //=), but /= cannot store a fractional result in
an integer variable.
assert 9 / 2 == 4.5
assert 9 // 2 == 4
assert -9 // 2 == -5
assert 5.5 // 2.0 == 2.0
assert -5.5 // 2.0 == -3.0
assert -5.5 % 2.0 == 0.5
assert 5.5 % -2.0 == -0.5
assert type(5.5f // 2.0f) == Float
value = 9.0
value /= 2.0
assert value == 4.5
Floating-point floor division operates on the represented binary values: for example,
1.0 // 0.1 is 9.0, because the represented divisor is slightly
greater than one tenth. A literal zero divisor is a compilation SyntaxError;
a zero obtained at runtime raises ZeroDivisionException, including
-0.0. Signed integer MIN_VALUE // -1 raises ValueException
because the quotient cannot fit its type. Floating-point NaN and infinite operands follow
floating-point arithmetic, without conversion to integer storage.
The % operator is paired with floor division: its nonzero result has the
divisor's sign, for integers and floating-point values. For finite floating-point operands,
a == (a // b) * b + (a % b) holds subject to rounding; a zero remainder has
the divisor's sign. Compound assignment %= follows the same rule.
Math.fmod remains the distinct truncating-remainder operation.
Mixed Int/UInt arithmetic produces Long:
-5 // 2u == -3l and -5 % 3u == 1l.
A signed integer combined with ULong requires an explicit conversion,
such as -5l // Long(2ul). See
numeric types for the promotion rules.
Integer exponentiation ** requires a nonnegative integer exponent.
A negative constant exponent is a compilation TypeError; a negative
exponent obtained at runtime raises ValueException, including with
**=. Use a floating-point operand for reciprocals, such as
2.0 ** -1 == 0.5. Unary minus requires a signed numeric operand:
explicitly convert unsigned values before negating them. Char and
CodeUnit are not numbers; convert them explicitly for arithmetic.
Adding two Char values still concatenates them into a String.
Comparisons
value = 5
assert value == 5
assert value != 6
assert value < 10
assert value <= 5
assert value >= 1
assert 1 < value < 10
Chained comparisons are a built-in language feature and are ideal for range-style conditions.
Numeric comparisons preserve integer precision across signed, unsigned, and floating-point
operands. NaN is the exception: it is unordered, so every ordered comparison involving
nan or nanf returns false. See
NaN and Infinity.
Boolean Logic, Identity, Membership
ready = true
connected = true
failed = false
text = "hello"
assert ready and connected
assert not failed
assert text is String
assert text is not null
assert 10 in [10, 20, 30]
assert "green" not in {"red", "blue"}
Klyn uses and, or, and not. C-style
&& and || are rejected.
Strings and Interpolation
Ordinary string, Char and CodeUnit literals support
\0 (NUL), \a (bell), \b (backspace),
\t (tab), \n (line feed), \v (vertical tab),
\f (form feed) and \r (carriage return).
Escape backslashes and delimiters with \\, \",
\' or \`. Raw strings preserve backslashes literally;
unknown escape sequences are compilation errors.
assert "A\0B".size == 3ul
assert '\b'.toInt() == 8
assert r"\n" == "\\n"
name = "Ada"
score = 7
ratio = 3.14159
print(f"Player: {name}")
print(f"Score: {score:03d}")
print(f"Ratio: {ratio:.4f}")
Numeric widths use spaces unless a leading 0 explicitly requests
zero padding. This rule is the same for decimal, binary, octal and hexadecimal:
assert f"{10:2x}" == " a"
assert f"{10:02x}" == "0a"
assert f"{10:2X}" == " A"
assert f"{10:02X}" == "0A"
The alignment markers <, > and ^
apply to numbers as well as text. Numeric formats require a primitive numeric
value, not an unrelated class with the same short name. Integer formatting
preserves the full range of unsigned types. Floating formats convert integer
values to Double and can round integers beyond its exact range.
assert f"{-42:<6d}" == "-42 "
assert f"{-42:^06d}" == "0-4200"
maximum = ULong.MAX_VALUE
assert f"{maximum:d}" == "18446744073709551615"
assert f"{maximum:x}" == "ffffffffffffffff"
Precision is not valid for integer formats (d, b,
o, x and X); it raises TypeError.
For text, precision limits the number of Unicode characters before alignment
is applied. The s marker is optional when the value is text.
text = "abcdef"
assert f"{text:.2s}" == "ab"
assert f"{text:.2}" == "ab"
assert f"{text:>5.2s}" == " ab"
Widths and precisions are non-negative integers up to Int.MAX_VALUE;
sizes outside that range are rejected with TypeError. Accepted
fields are not silently reduced. Allocating the resulting text can still fail
with OutOfMemoryError. Nullable references use the same textual
conversion as String(value), including the text "null".
assert f"{1:1024d}".size == 1024ul
assert f"{1.0:.100f}".size == 102ul
missing as String = null
assert f"{missing}" == "null"
Double braces {{ and }} produce literal braces,
as in Python. Single braces enclose an expression. Parenthesize a set or map
literal at the start of an expression to avoid confusing it with escaped braces.
value = 42
assert f"{{{value}}}" == "{42}"
assert f"{{notAnIdentifier}}" == "{notAnIdentifier}"
assert f"{({1: "ok"})[1]}" == "ok"
assert f"{({10, 10})}" == "{10}"
assert f"{r"\n"}" == "\\n"
Interpolated strings are often the clearest way to build small textual messages without manual
concatenation. Both "..." and f"..." produce immutable
String values. Use m"..." for editable text, or combine the
prefixes as fm"..." to create an interpolated MutableString.
editable = fm"Player: {name}"
snapshot = editable.toString()
editable[0] = 'p'
assert editable == "player: Ada"
assert snapshot == "Player: Ada" # Later edits do not change this String.
path = rm"C:\Users\Ada" # Mutable raw text: backslashes are literal.
The f (interpolation), r (raw text), and m
(mutable text) prefixes can be combined in any order, once each, including with
triple-double-quoted strings. For example, frm"{name}\n" interpolates
name but retains the literal backslash and n. See
Immutable and Mutable Text for the
mutation API, reference sharing, and immutable snapshots.
Calls, Members, and Indexing
user.name
lines[0]
data[-1]
config["theme"]
result = compute(10)
Operator Precedence
The following table is ordered from the strongest binding level to the weakest. Operators on the same row have the same precedence; associativity decides how an unparenthesized sequence at that level is grouped. Parentheses always override these defaults and are recommended whenever they make an expression's intent easier to read.
| Operators | Associativity | Purpose |
|---|---|---|
Highest priority., ::, [] |
Left to Right | Member access, binding references, indexing, and slicing. |
++, -- | Right to Left (prefix); Left to Right (postfix) | Prefix or postfix update. |
+value, -value, ~value | Right to Left | Unary numeric and bitwise operators. |
** | Right to Left | Exponentiation. |
*, /, //, %, @ | Left to Right | Multiplication, division, remainder, and matrix multiplication. |
+, - | Left to Right | Addition and subtraction. |
<<, >> | Left to Right | Bit shifts. |
<, <=, >, >=, is, is not, in, not in | Left to Right; ordering may chain | Ordering, type or identity checks, and membership. |
==, !=, =~ | Left to Right | Equality and regular-expression matching. |
& | Left to Right | Bitwise AND. |
^ | Left to Right | Bitwise XOR. |
| | Left to Right | Bitwise OR. |
not | Right to Left | Boolean negation. |
and | Left to Right | Short-circuit Boolean AND. |
or | Left to Right | Short-circuit Boolean OR. |
thenValue if condition else elseValue | Right to Left | Conditional expression. |
Lowest priority+=, -=, *=, /=, //=, %=, **=, @=, <<=, >>=, &=, ^=, |= |
Right to Left | Compound mutation. |
assert 2 + 3 * 4 == 14
assert 2 ** 3 ** 2 == 512 # 2 ** (3 ** 2)
assert -2 ** 2 == 4 # (-2) ** 2: unary minus binds first
assert 1 << 2 + 1 == 8 # 1 << (2 + 1)
assert true or false and false # true or (false and false)
assert not 3 < 2 # not (3 < 2)
Calls are parsed with their primary expression and therefore bind before the operators above.
An as cast applies to the completed call/member chain before surrounding arithmetic.
The = token used in a named call argument, together with : and
, delimiters, is parser syntax rather than a general value operator.
An expression such as minimum < probe.value() < maximum is not rewritten as two
independent calls. Klyn evaluates probe.value() once and reuses that result for both
comparisons. Exponentiation and conditional expressions associate to the right; ordinary
arithmetic, equality, bitwise, and Boolean binary operators associate to the left.
Slices
Strings, fixed arrays, and dynamic array lists support Python-style
[start:stop:step] slices. Bounds may be omitted and negative indexes are resolved
from the end. The stop index is exclusive.
text = "Hello World"
assert text[0:5] == "Hello"
assert text[::-1] == "dlroW olleH"
values = [10, 20, 30, 40, 50]
assert values[1:4] == [20, 30, 40]
assert values[::2] == [10, 30, 50]
values[1:4] = [200, 300, 400]
assert values == [10, 200, 300, 400, 50]
Conditional Expressions
message = "small" if value < 10 else "large"
Operator Overloading
Classes may define typed operator methods without changing expression precedence.
See Operator Overloading in the Types and OOP section
for supported operators, indexed access, callable instances, and the distinct declarations
for ++value, value++, --value, and value--.