Core syntax Operators

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"}
Use language keywords

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.

Evaluation order Operator binding order
Read from top to bottom
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, ~valueRight to LeftUnary numeric and bitwise operators.
**Right to LeftExponentiation.
*, /, //, %, @Left to RightMultiplication, division, remainder, and matrix multiplication.
+, -Left to RightAddition and subtraction.
<<, >>Left to RightBit shifts.
<, <=, >, >=, is, is not, in, not inLeft to Right; ordering may chainOrdering, type or identity checks, and membership.
==, !=, =~Left to RightEquality and regular-expression matching.
&Left to RightBitwise AND.
^Left to RightBitwise XOR.
|Left to RightBitwise OR.
notRight to LeftBoolean negation.
andLeft to RightShort-circuit Boolean AND.
orLeft to RightShort-circuit Boolean OR.
thenValue if condition else elseValueRight to LeftConditional 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.

Chained comparisons evaluate intermediate operands once

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--.