# OxigenLang Data Types

OxigenLang supports a rich set of data types for building expressive programs. The language is dynamically typed by default, but offers optional type annotations to lock a variable's type. See the [Type System](https://www.oxigenlang.org/docs/type-system/) guide for details on type annotations.

## Primitive Types

### Integer

64-bit signed integers. Supports standard arithmetic, comparison, and postfix increment/decrement.

```oxi
x := 42
y := -7
z := 0
```

Type keyword: `int`. Zero value: `0`.

Range is `-9223372036854775808` to `9223372036854775807`. `+`, `-`, and `*` wrap
around on overflow; `/` and `%` raise an error instead. See
[Integer Overflow](https://www.oxigenlang.org/docs/operators/#integer-overflow) for the exact rules.

### Float

64-bit floating-point numbers. Supports arithmetic and comparison operations.

```oxi
pi := 3.14
neg := -0.5
big := 1.5e3      // 1500.0; an exponent makes any literal a float
tiny := 2E-4
```

Type keyword: `float`. Zero value: `0.0`. A literal too large for a 64-bit float is a compile error.

Floats and integers can be mixed in arithmetic — the result follows standard promotion rules.

### String

Unicode strings. Can be enclosed in double quotes or single quotes — both are equivalent:

```oxi
greeting := "hello"
name := 'world'
```

Type keyword: `str`. Zero value: `""` (empty string).

Single- and double-quoted strings are **single-line**: a raw newline before the closing quote is an error (write newlines with the `\n` escape instead).

#### Multi-line Strings

Wrap text in triple quotes — `"""..."""` or `'''...'''` — to write a string that spans multiple lines. Raw newlines inside the string are kept verbatim, so you don't need `\n`:

```oxi
poem := """
Roses are red,
Violets are blue.
"""
println(poem)
```

The content is taken **literally**, including the leading newline right after the opening `"""` and the trailing newline before the closing `"""`. Put the text on the same line as the fences to avoid them:

```oxi
banner := """+----------+
|  Oxigen  |
+----------+"""
```

Triple-quoted strings support the **same interpolation** and escape sequences as single-line strings, and an interpolation expression may itself span multiple lines:

```oxi
name := "world"
x := 10
y := 20

msg := """
Hello, {name}!
{x} + {y} = {x + y}
"""
println(msg)

// Interpolation expressions can span lines too:
total := """sum = {
    x +
    y
}"""
```

Both `"""` and `'''` behave identically; choose whichever lets you embed the other quote style without escaping. A triple-quoted string is only "unterminated" if end-of-file is reached before its closing fence.

#### String Operations

- **Concatenation**: Use `+` to join strings:

  ```oxi
  full := "hello" + " " + "world"
  ```

- **String Interpolation**: Use `{}` inside any string to embed expressions. The expression is evaluated and its result is converted to a string automatically:

  ```oxi
  name := "world"
  println("hello {name}")

  x := 10
  y := 20
  println("{x} + {y} = {x + y}")
  ```

  Interpolation works with variables, function calls, arithmetic, and any valid expression:

  ```oxi
  age := 25
  println("Next year you will be {age + 1}")

  items := [1, 2, 3]
  println("There are {len(items)} items")

  println("Type: {type(42)}")
  ```

  "Any valid expression" includes comparisons, `%`, and the logical keywords —
  there is no need to compute them into a variable first:

  ```oxi
  a := 1
  b := 2
  println("{a == b}")             // False
  println("{a < b and b % 2 == 0}")  // True
  println("{not (a == b)}")       // True
  println("{ {"k": 7}["k"] }")    // 7 — nested braces are fine
  ```

  Strings without `{}` remain plain strings — no special syntax is needed to opt out. Both double-quoted and single-quoted strings support interpolation.

- **Escape Sequences**: Strings support the following escape sequences:

  | Escape | Character |
  |--------|-----------|
  | `\n` | Newline |
  | `\t` | Tab |
  | `\r` | Carriage return |
  | `\e` | Escape (`ESC`, useful for ANSI terminal sequences) |
  | `\xHH` | Character by two hexadecimal digits, such as `\x1b` for `ESC` |
  | `\\` | Backslash |
  | `\"` | Double quote (inside `"..."`) |
  | `\'` | Single quote (inside `'...'`) |
  | `\0` | Null character |
  | `\{` | Literal `{` (a bare `{` would start an interpolation) |
  | `\}` | Literal `}` |

  ```oxi
  println("line one\nline two")
  println("col1\tcol2")
  println("she said \"hello\"")
  println('it\'s fine')
  println("\e[31mred\e[0m")
  println("\{not an interpolation\}")   // {not an interpolation}
  ```

  Escape sequences work in both plain strings and interpolated strings.

- **Indexing**: Access individual characters by position (returns a string):

  ```oxi
  "hello"[0]
  "hello"[4]
  ```

- **Slicing**: Extract a substring with `[start:end]`:

  ```oxi
  "hello world"[0:5]
  "hello world"[6:]
  "hello world"[:5]
  ```

- **Length**: Use `len()` to get the number of characters:

  ```oxi
  len("hello")
  ```

- **To characters**: Use `chars()` to convert to an array of characters:

  ```oxi
  chars("abc")
  ```

- **To string**: Use `str()` to convert other types to their string representation:

  ```oxi
  str(42)
  str(True)
  ```

### Character

A single Unicode character, enclosed in backticks:

```oxi
letter := `a`
digit := `5`
symbol := `@`
```

Type keyword: `char`. Zero value: `\0` (null character).

Characters are distinct from single-character strings. Use `ord()` to get the Unicode codepoint and `chr()` to convert back:

```oxi
ord(`a`)
chr(97)
```

### Boolean

Boolean values are capitalized: `True` and `False`:

```oxi
active := True
done := False
```

Type keyword: `bool`. Zero value: `False`.

#### Truthiness

All values in OxigenLang have a truthiness. The following are **falsy**:
- `False`
- `None`
- `0` (integer zero)
- `0.0` (float zero)
- `""` (empty string)
- `\0` (null character)
- `[]` (empty array)
- `()` (empty tuple)
- `{}` (empty map)
- `set()` (empty set)
- Any `Error` value

Everything else is **truthy**, including struct instances.

### None

Represents the absence of a value:

```oxi
x := None
```

Functions that don't explicitly return a value produce `None`. `None` is falsy.

## Collection Types

### Array

Dynamic, ordered collection of values. Elements can be of any type:

```oxi
nums := [1, 2, 3]
mixed := [1, "hello", True, 3.14]
empty := []
```

Type keyword: `array`. Zero value: `[]`.

#### Array Operations

- **Indexing**: Access by position (zero-based):

  ```oxi
  arr := [10, 20, 30]
  arr[0]
  arr[2]
  ```

- **Index Assignment**: Update an element at a specific position:

  ```oxi
  arr := [10, 20, 30]
  arr[0] = 99
  arr[-1] = 77       // negative index counts from the end
  println(arr)        // [99, 20, 77]
  ```

- **Out-of-range indices are an error**, for both reads and writes. This applies
  to arrays, tuples, and strings; negative indices are valid down to `-len`:

  ```oxi
  arr := [10, 20, 30]
  arr[3]              // error: index 3 out of range for ARRAY of length 3
  arr[-4]             // error: index -4 out of range for ARRAY of length 3
  arr[3] = 1          // error — an out-of-range write does not append
  ```

  Use `len(arr)` to bounds-check, or `push(arr, value)` to grow an array.
  Slicing clamps instead of erroring, so `arr[0:99]` is the whole array. Maps
  are unaffected: a missing key is a lookup miss, and still returns `None`.

- **Slicing**: Extract a sub-array with `[start:end]`:

  ```oxi
  arr := [10, 20, 30, 40, 50]
  arr[1:3]
  arr[2:]
  arr[:2]
  arr[:]
  ```

  Start and end can be any expression. Out-of-bounds values are clamped silently.

- **Add**: `push` appends, `insert` puts an element at an index, and `extend`
  appends many. All three change the array in place:

  ```oxi
  arr := [1, 2]
  push(arr, 3)            // [1, 2, 3]
  insert(arr, 0, 0)       // [0, 1, 2, 3]
  extend(arr, [4, 5])     // [0, 1, 2, 3, 4, 5]
  ```

- **Remove**: `pop` removes by position and returns the element; `remove`
  removes the first element equal to a value; `clear` empties the array:

  ```oxi
  arr := [10, 20, 30, 20]
  pop(arr)                // 20 (the last); arr is [10, 20, 30]
  pop(arr, 0)             // 10; arr is [20, 30]
  remove(arr, 30)         // arr is [20]
  clear(arr)              // arr is []
  ```

- **Copy**: arrays are shared references, so `b := a` gives two names for one
  array. `copy(a)` makes a separate one.

- **First / Last / Rest**:

  ```oxi
  first([1, 2, 3])
  last([1, 2, 3])
  rest([1, 2, 3])
  ```

- **Length**: `len([1, 2, 3])` returns `3`.

- **Membership**: `has([1, 2, 3], 2)` returns `True`. The
  [`array`](https://www.oxigenlang.org/docs/stdlib/array/) module searches with a predicate: `find`,
  `find_index`, `index_of`, `filter`.

### Tuple

Fixed-size, immutable ordered collection. Created with parentheses or the `tuple()` built-in:

```oxi
point := (1, 2)
record := (1, "hello", True)
single := (42,)
empty := ()
```

Type keyword: `tuple`. Zero value: `()`.

Note: A single-element tuple requires a trailing comma to distinguish it from a grouped expression.

#### Tuple Operations

- **Indexing**: `(1, 2, 3)[0]` returns `1`.
- **Slicing**: `(1, 2, 3, 4)[1:3]` returns `(2, 3)`.
- **Concatenation**: `(1, 2) + (3, 4)` returns `(1, 2, 3, 4)`.
- **Length**: `len((1, 2))` returns `2`.
- **Membership**: `has((10, 20), 10)` returns `True`.

### Map

Key-value pairs, enclosed in curly braces. A value can be any type; a key must
be **hashable**:

```oxi
person := {"name": "Alice", "age": 30}
mixed := {1: "one", "two": 2}
tuple_key := {(1, 2): "point"}
empty := {}
```

The hashable kinds are `int`, `uint`, `float`, `bool`, `char`, `byte`, `str`,
`None`, and tuples whose elements are all hashable. Arrays, maps, sets, struct
instances, and functions are not — they are mutable, so a key could be changed
after insertion and no longer match its own entry. Using one is an error
(`E0035`), not a silent fallback:

```oxi
m := {[1, 2]: "v"}   // error[E0035]: ARRAY is not hashable
```

Type keyword: `map`. Zero value: `{}`.

Note: An empty map `{}` and an empty block `{}` use the same syntax. The parser distinguishes them by context — at the expression level, and in an `option`/`choose` arm, `{}` is a map literal.

#### Map Operations

- **Indexing**: Access by key:

  ```oxi
  m := {"a": 1, "b": 2}
  m["a"]
  ```

- **Index Assignment**: Set or update a key directly using bracket syntax:

  ```oxi
  m <map>
  m["name"] = "Oxigen"
  m["version"] = "0.1.0"
  m["version"] = "0.2.0"    // updates existing key
  ```

  Both `=` and `:=` work for index assignment.

- **Dot Access (string keys)**: Read and write string-keyed entries with `.name` syntax:

  ```oxi
  y <map> := {"name": "oxigen"}
  y.name              // "oxigen"  (same as y["name"])
  y.version = "0.1"   // inserts the key
  ```

  Dot access is limited to string keys. Non-string keys still require bracket syntax.

  **Reading a missing key with the dot form is an error**, the same as a missing
  struct field — so a mistyped key does not pass silently:

  ```oxi
  y.mising            // error: key 'mising' not found on map
  ```

  Bracket access is a lookup and still answers `None` for an absent key, so use
  it (or `has`) whenever a key may legitimately be missing. Writing through the
  dot form still inserts:

  ```oxi
  y["mising"]         // None
  has(y, "mising")    // False
  y.author = "ada"    // inserts, no error
  ```

- **Insert**: Adds the key-value pair, or updates it, in place. `extend`
  copies in every entry of another map:

  ```oxi
  m := {"a": 1}
  insert(m, "b", 2)            // {a: 1, b: 2}
  extend(m, {"c": 3})          // {a: 1, b: 2, c: 3}
  ```

- **Remove**: `remove` deletes a key in place; `pop` deletes it and returns
  its value (`None` if it wasn't there); `clear` empties the map:

  ```oxi
  m := {"a": 1, "b": 2}
  remove(m, "a")               // {b: 2}
  pop(m, "b")                  // 2; m is {}
  ```

- **Keys / Values**:

  ```oxi
  keys({"a": 1, "b": 2})
  values({"a": 1, "b": 2})
  ```

- **Length**: `len({"a": 1})` returns `1`.
- **Membership**: `has({"a": 1}, "a")` returns `True` (checks keys).
- **Copy**: `copy(m)` makes a separate map; `n := m` shares one.

### Set

A collection of unique values. Created with the `set()` built-in, from values
or from an array:

```oxi
s := set(1, 2, 3)
duped := set(1, 1, 2, 2)
from_array := set([1, 2, 2, 3])
empty := set()
```

Type keyword: `set`. Zero value: `set()`.

Duplicates are automatically removed on creation. A set remembers the order
values were added in, which is the order `each` and `println` show them.
Two sets are equal when they hold the same values, in any order.

Set elements must be hashable, by the same rule and for the same reason as map
keys — see [Map](#map) above.

#### Set Operations

- **Add**: `push(s, x)` or `insert(s, x)` adds a value; adding one the set
  already holds does nothing. `extend(s, items)` adds every element of an
  array, tuple or set.
- **Remove**: `remove(s, x)` deletes a value; `pop(s)` deletes and returns the
  most recently added one; `clear(s)` empties the set.
- **Membership**: `has(set(1, 2, 3), 2)` returns `True`.
- **Length**: `len(set(1, 2, 3))` returns `3`.
- **Copy**: `copy(s)` makes a separate set.

All of these change the set in place:

```oxi
s := set(1, 2)
push(s, 3)             // set(1, 2, 3)
remove(s, 1)           // set(2, 3)
extend(s, [3, 4])      // set(2, 3, 4)
```

Union, intersection, difference and subset tests are in the
[`sets`](https://www.oxigenlang.org/docs/stdlib/sets/) module:

```oxi
introduce sets

sets.union(set(1, 2), set(2, 3))          // set(1, 2, 3)
sets.intersection(set(1, 2), set(2, 3))   // set(2)
```

### Byte

Unsigned 8-bit integer (0–255). Created with the `byte()` built-in:

```oxi
b := byte(65)
```

Type keyword: `byte`. Zero value: `0`.

Byte values can be created from integers (0–255), unsigned integers, and characters:

```oxi
byte(65)
byte(uint(200))
byte(`A`)
```

Values outside 0–255 produce an error.

### Uint

Unsigned 64-bit integer. Created with the `uint()` built-in:

```oxi
u := uint(42)
```

Type keyword: `uint`. Zero value: `0`.

Uint values can be created from non-negative integers, floats (truncated), strings (parsed), and bytes:

```oxi
uint(42)
uint(3.7)
uint("100")
uint(byte(255))
```

Negative values produce an error.

Range is `0` to `18446744073709551615`. `+` and `*` wrap around on overflow, but
a subtraction that would go below zero is an error rather than a wrap. See
[Unsigned Integer Overflow](https://www.oxigenlang.org/docs/operators/#unsigned-integer-overflow).

## Vectors

`vec2`, `vec3` and `vec4` hold two, three or four numbers: positions,
directions, velocities and colours. Build one from ints or floats:

```oxi
position := vec3(0, 1.5, -2)
velocity := vec3(1, 0, 0)
```

Read components with `.x`, `.y`, `.z` and `.w`. They come back as floats:

```oxi
position.y   // 1.5
```

Vectors are values, like numbers: operators make new vectors, and a component
cannot be assigned. Build a new vector instead:

```oxi
position = position + velocity * 0.5
position = vec3(0, position.y, position.z)   // position.x = 0 is an error
```

| Expression | Result |
|---|---|
| `a + b`, `a - b` | Component by component; both the same size |
| `a * b` | Component by component; both the same size |
| `v * n`, `n * v`, `v / n` | Every component scaled by the number `n` |
| `-v` | Every component negated |
| `a == b` | Same size and the same components |

Mixing sizes (`vec2(1, 2) + vec3(1, 2, 3)`) is a type mismatch error.
`type()` reports `VEC2`, `VEC3` or `VEC4`. The `math` module has
`dot`, `cross`, `length`, `distance`, `normalize` and `lerp` for vectors; see
[math](https://www.oxigenlang.org/docs/stdlib/math/#vectors).

Components are stored as 32-bit floats, as graphics hardware uses them, so a
component reads back as the nearest 32-bit value: `vec2(0.1, 0).x` is
`0.10000000149011612`. Vectors display the shorter form, `vec2(0.1, 0)`.

## Enums

Enumerations define a fixed set of named variants. See [Enums](https://www.oxigenlang.org/docs/enums/) for the full reference.

```oxi
enum Stoplight { Red: 0, Yellow: 1, Green: 2 }

light <Stoplight> := Stoplight.Green
light.value   // 2
```

Variants may carry tuple or struct payloads (Rust-style). Type annotations support both `<Enum>` (any enum) and `<EnumName>` (a specific enum).

## Type Introspection

Use `type()` to get the string name of any value's type:

```oxi
type(42)
type("hello")
type(3.14)
type(`a`)
type(True)
type(None)
type([1, 2])
type((1, 2))
type({"a": 1})
type(set(1, 2))
type(byte(65))
type(uint(42))
```

For struct instances, `type()` returns the struct name instead of a generic type:

```oxi
struct Person { name <str> }
p := Person("Alice")
type(p)
```

Use `is_type()` to check a value's type directly against a type keyword:

```oxi
is_type(42, int)
is_type("hello", str)
is_type(3.14, float)
```

See also:
- [Variables and Assignments](https://www.oxigenlang.org/docs/variables/) — how to declare variables of these types
- [Type System](https://www.oxigenlang.org/docs/type-system/) — type annotations, conversions, and mutability
- [Operators](https://www.oxigenlang.org/docs/operators/) — operations supported by each type
- [Built-in Functions](https://www.oxigenlang.org/docs/builtins/) — all functions for working with these types
