Reference

Expressions

Generated from LANGUAGE.md at bf2cba0, 2026-08-22.

Literals

TypeExample
Integer42, 0, -5
Float3.14, -2.5, 0.0
String"Hello, World!"
Booleantrue, false
Hexadecimal0xFF, 0xDEADBEEF
Binary0b10110100, 0b1111
Character'A', '!'

Note: Float literals are recognized by the presence of a decimal point. Floats and integers can be mixed in arithmetic expressions.

Note: Arithmetic operates on numbers (booleans count as 0/1). Text, buffers, and lists must be cast with as a number or as a float before they can be used in arithmetic - using them directly is a compile error, since they hold pointers rather than numeric values.

Hex and Binary:

  • Hexadecimal literals use 0x prefix: 0xFF equals 255
  • Binary literals use 0b prefix: 0b1010 equals 10
  • Character literals use single quotes: 'A' equals 65

Variable Reference

  • the x - references the variable x
  • the number - references loop iterator (inside for each)
  • x - direct identifier reference

Arithmetic

the x add 5
y subtract 3
the lhs multiply rhs
total divide 2
x modulo 3
{x add y} multiply z
{fibonacci of n subtract 1} add {fibonacci of n subtract 2}

Note: the is optional before variable names in expressions.

For complex arithmetic subexpressions, use curly braces {...} to group each subexpression. A cast (as a <type>) binds tighter than arithmetic and applies to the expression immediately to its left, so s as a number add 1 casts s and then adds 1. To cast a whole arithmetic expression, brace it: {a add b} as a number. Comma-separated arithmetic continuation (for example ..., add ...) is not valid syntax.

Comparisons

the x is greater than 5
y is less than 10
lhs is equal to rhs
x is 0

Note: the is optional before variable names in comparisons.

Property Checks

the x is even
the y is odd
the z is positive
the n is negative
the value is zero
the list is empty

Logical Operators

<condition> and <condition>    (true if both conditions are true)
<condition> or <condition>     (true if either condition is true)
not <condition>                (true if condition is false)

not takes the whole condition after it, exactly as the fence above says and exactly as English does: If not heat is limit then, reads "if it is not the case that heat is limit", never "if the negation of heat is limit". So not binds looser than every comparison and property check, and tighter than and and ornot heat is 4 and limit is 6 is {not (heat is 4)} and (limit is 6). A not in front of a boolean is that same rule with the shortest condition: If not door_open then,.

A not always answers a boolean, whatever it is applied to: not 5 and not greeting are booleans, not a number and a text. On a text, list, map or buffer, not tests the value's pointer — which a declared variable always has — so it answers false whether or not the collection holds anything. Ask about contents with is empty (see Property Checks above), never with not.

Plural Comparisons with are

Test multiple variables against the same value using comma-separated subjects:

if x, y, and z are true
if a, b, and c are not false
if 'door open', lift_moving, and lift_full are not true

Expansion:

if x, y, and z are true

expands internally to:

if x is true and y is true and z is true

Rules:

  • Subjects are separated by commas
  • The word and before the last subject is optional but recommended for natural language readability
  • The predicate after are applies to ALL subjects
  • are not negates the comparison for all subjects

Type Casting

Convert values between types using the as or in keywords.

Syntax:

<value> as a <type>
<value> as <type>
<value> in <unit>

Basic Conversions:

FromToSyntaxResult
floatnumber3.14 as a number3 (truncated)
numberfloat42 as a float42.0
numbertext25 as text"25"
textnumber"123" as a number123
floattext3.14 as text"3.14"
textfloat"3.14" as a float3.14
booleannumbertrue as a number1
booleannumberfalse as a number0
numberboolean0 as a booleanfalse
numberboolean42 as a booleantrue
booleantexttrue as text"true"
textboolean"true" as a booleantrue
buffertextdata as texta copy of the buffer's bytes

A text made from a buffer is an independent copy, not a window onto the buffer. a text called line is data as text. reads the buffer's current bytes once and keeps its own copy, so clearing, refilling, or resizing data afterwards leaves line exactly as it was — the same promise format strings make (see "Format Strings as Values"). This matters because resizing frees the buffer's old allocation: without the copy, reading such a text would be reading freed memory.

The cast is optional for this one conversion. Every spelling that puts a buffer into a slot that holds text means the same thing and makes the same copy — a text called line is data., Set line to data., the line is data., a text parameter given a buffer argument, and Return a text, data. — as do data as text and "{data}". Writing the cast is still good style where the type change is worth pointing at, but leaving it out never changes what the sentence does. This does not loosen type immutability: line is text before the write and text after it, and every other mismatched write is still the compile error described under "Type Immutability".

A value slot is one of those slots. A buffer written into a value — by declaration, by Set, by the ... is, as a value argument, or by Return a value — arrives as text and reports Text (dynamic), carrying the same independent copy of the buffer's bytes. A value never holds a buffer as a buffer; there is no Buffer (dynamic) tag.

A float read from text is the same double as the literal. "0.88" as a float and the literal 0.88 are one value, and comparing them with is finds them equal: the runtime reads a decimal exactly the way the compiler reads one written in the source. The guarantee covers up to eighteen significant digits with the point up to twenty-two places away from them - wider than a float can tell apart - and a longer decimal is read as the nearest float those eighteen digits describe. This is what lets a number read from a file, an argument or an environment variable be compared against a literal in the same program.

Radix (Base) Conversions:

Text-to-number casting isn't limited to base 10. A radix word can be inserted right before number to parse in a different base:

SyntaxBaseExampleResult
as a number10 (default)"42" as a number42
as a hex number / as a hexadecimal number16"ff" as a hex number255
as an octal number8"17" as an octal number15
as a binary number2"1010" as a binary number10
as a base N number (spaced)any 2-36"z9a" as a base 36 number45694
as a baseN number (fused)any 2-36"6543" as a base7 number2334

Any base from 2 through 36 is supported, not just the aliased ones (hex/octal/binary) - digits above 9 use letters a-z (case- insensitive), so base 36 is the practical maximum for a single- character-per-digit representation.

(Hex string to number)
a text called hexstr is "3fa2c1e4".
a number called n is hexstr as a hex number.

(Arbitrary base, fused or spaced form - both work)
a text called s is "6543".
a number called n2 is s as a base7 number.
a number called n3 is s as a base 7 number.

(Negative numbers and uppercase hex digits both work)
a text called neg is "-1a".
a number called n4 is neg as a hex number.   (-26)
a text called upper is "FF".
a number called n5 is upper as a hex number. (255)

Like the base-10 case, parsing stops at the first character invalid for that base rather than raising an error - "12g5" as a hex number gives 18 (stops at g), and a string that's invalid from its very first character (e.g. "abc" as a base5 number, since a's value of 10 is too big for base 5) gives 0.

Examples:

(Float to number - truncates)
a float called pi is 3.14159.
a number called 'pi truncated' is pi as a number.

(Number to text)
a number called age is 25.
a text called agestr is the age as text.

(Text to number - parsing)
a text called userinput is "123".
a number called parsed is the userinput as a number.

(Boolean to number)
a boolean called done is true.
a number called 'done num' is the done as a number.

(Inline casting)
Print 3.14159 as a number.

The in Keyword:

The in keyword reads more naturally for timer duration casts. It applies to a timer's duration or elapsed, not to a plain number:

(Duration from timer)
Print the timer's duration in seconds.
Print the timer's elapsed in milliseconds.

in only works on a timer's duration/elapsed (it lowers to a duration cast); <number> in <unit> on a plain number is not valid syntax. To convert a plain number of milliseconds to seconds, divide: the millis divide 1000.

Formatted Output:

Numbers can be converted to padded text for display formatting with the zero-pad format specifier:

(Pad to 2 digits - for times like 09:05)
a number called h is 9.
a text called hpadded is "{h:02}".
Print the hpadded.  (prints "09")

Casting Rules:

  • as a <type> and as <type> are equivalent (article is optional)
  • A cast binds tighter than arithmetic and applies to the expression immediately to its left: n as a number add 1 is (n as a number) add 1. Brace to cast a whole expression: {a add b} as a number
  • Float to number truncates (does not round)
  • To round: add 0.5 before casting ({3.7 add 0.5} as a number4)
  • Text, buffers, and lists cannot be used directly in arithmetic; cast them with as a number / as a float first
  • Text to number fails if text is not a valid number (sets error flag)
  • Text to number in a non-default base (as a hex/octal/binary/base N number) stops parsing at the first character invalid for that base, rather than failing outright - it does not set the error flag
  • Zero is false, any non-zero number is true
  • in keyword is for timer duration/elapsed casts (see above)