# Expressions

This page is the "Expressions" section of the Voxlang language reference. It is generated from LANGUAGE.md in the Voxlang compiler's own repository, so it says what the specification says and nothing more.

A URL is the heading, lowercased, with punctuation dropped and spaces turned into hyphens. Each "##" section of the spec is a page under /docs/, and every heading inside it is a fragment on that page, so "File I/O" is /docs/file-io/ and "Reading a whole file" inside it is /docs/file-io/#reading-a-whole-file.

As data: https://vox-lang.dev/docs/index.json lists every section and every heading with its URL, https://vox-lang.dev/docs/search.json carries one entry per heading with its first sentence and keywords, and https://vox-lang.dev/docs/anchors.json maps every slug to the page it lives on. Search is also a JSON endpoint: https://vox-lang.dev/docs/search?q=<words> returns ranked results as JSON, no page load.

Source: LANGUAGE.md at bf2cba0, 2026-08-22. https://github.com/Vox-lang/vox/blob/bf2cba037976586c30d22a48e7b7246d347f54a1/LANGUAGE.md

[Reference](https://vox-lang.dev/docs/) > Expressions

## Literals

| Type | Example |
| --- | --- |
| Integer | `42`, `0`, `-5` |
| Float | `3.14`, `-2.5`, `0.0` |
| String | `"Hello, World!"` |
| Boolean | `true`, `false` |
| Hexadecimal | `0xFF`, `0xDEADBEEF` |
| Binary | `0b10110100`, `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

```vox fragment
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

```vox fragment
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

```vox fragment
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

```vox fragment
<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 `or` — `not 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:

```vox fragment
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:**

```vox fragment
if x, y, and z are true
```

expands internally to:

```vox fragment
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:**

```vox fragment
<value> as a <type>
<value> as <type>
<value> in <unit>
```

**Basic Conversions:**

| From | To | Syntax | Result |
| --- | --- | --- | --- |
| float | number | `3.14 as a number` | `3` (truncated) |
| number | float | `42 as a float` | `42.0` |
| number | text | `25 as text` | `"25"` |
| text | number | `"123" as a number` | `123` |
| float | text | `3.14 as text` | `"3.14"` |
| text | float | `"3.14" as a float` | `3.14` |
| boolean | number | `true as a number` | `1` |
| boolean | number | `false as a number` | `0` |
| number | boolean | `0 as a boolean` | `false` |
| number | boolean | `42 as a boolean` | `true` |
| boolean | text | `true as text` | `"true"` |
| text | boolean | `"true" as a boolean` | `true` |
| buffer | text | `data as text` | a 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:

| Syntax | Base | Example | Result |
| --- | --- | --- | --- |
| `as a number` | 10 (default) | `"42" as a number` | `42` |
| `as a hex number` / `as a hexadecimal number` | 16 | `"ff" as a hex number` | `255` |
| `as an octal number` | 8 | `"17" as an octal number` | `15` |
| `as a binary number` | 2 | `"1010" as a binary number` | `10` |
| `as a base N number` (spaced) | any 2-36 | `"z9a" as a base 36 number` | `45694` |
| `as a baseN number` (fused) | any 2-36 | `"6543" as a base7 number` | `2334` |

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 number` → `4`)
- 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)
