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 guide for details on type annotations.
#Primitive Types
#Integer
64-bit signed integers. Supports standard arithmetic, comparison, and postfix increment/decrement.
x := 42
y := -7
z := 0Type keyword: int. Zero value: 0.
Range is -9223372036854775808 to 9223372036854775807. +, -, and * wrap
around on overflow; / and % raise an error instead. See
Integer Overflow for the exact rules.
#Float
64-bit floating-point numbers. Supports arithmetic and comparison operations.
pi := 3.14
neg := -0.5
big := 1.5e3 // 1500.0; an exponent makes any literal a float
tiny := 2E-4Type 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:
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:
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:
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:
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: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: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:
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: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 fineStrings 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 \nNewline \tTab \rCarriage return \eEscape ( ESC, useful for ANSI terminal sequences)\xHHCharacter by two hexadecimal digits, such as \x1bforESC\\Backslash \"Double quote (inside "...")\'Single quote (inside '...')\0Null character \{Literal {(a bare{would start an interpolation)\}Literal }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):
"hello"[0] "hello"[4] -
Slicing: Extract a substring with
[start:end]:"hello world"[0:5] "hello world"[6:] "hello world"[:5] -
Length: Use
len()to get the number of characters:len("hello") -
To characters: Use
chars()to convert to an array of characters:chars("abc") -
To string: Use
str()to convert other types to their string representation:str(42) str(True)
#Character
A single Unicode character, enclosed in backticks:
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:
ord(`a`)
chr(97)#Boolean
Boolean values are capitalized: True and False:
active := True
done := FalseType keyword: bool. Zero value: False.
#Truthiness
All values in OxigenLang have a truthiness. The following are falsy:
FalseNone0(integer zero)0.0(float zero)""(empty string)\0(null character)[](empty array)()(empty tuple){}(empty map)set()(empty set)- Any
Errorvalue
Everything else is truthy, including struct instances.
#None
Represents the absence of a value:
x := NoneFunctions 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:
nums := [1, 2, 3]
mixed := [1, "hello", True, 3.14]
empty := []Type keyword: array. Zero value: [].
#Array Operations
-
Indexing: Access by position (zero-based):
arr := [10, 20, 30] arr[0] arr[2] -
Index Assignment: Update an element at a specific position:
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: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 appendUse
len(arr)to bounds-check, orpush(arr, value)to grow an array. Slicing clamps instead of erroring, soarr[0:99]is the whole array. Maps are unaffected: a missing key is a lookup miss, and still returnsNone. -
Slicing: Extract a sub-array with
[start:end]: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:
pushappends,insertputs an element at an index, andextendappends many. All three change the array in place: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:
popremoves by position and returns the element;removeremoves the first element equal to a value;clearempties the array: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 := agives two names for one array.copy(a)makes a separate one. -
First / Last / Rest:
first([1, 2, 3]) last([1, 2, 3]) rest([1, 2, 3]) -
Length:
len([1, 2, 3])returns3. -
Membership:
has([1, 2, 3], 2)returnsTrue. Thearraymodule searches with a predicate:find,find_index,index_of,filter.
#Tuple
Fixed-size, immutable ordered collection. Created with parentheses or the tuple() built-in:
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]returns1. - Slicing:
(1, 2, 3, 4)[1:3]returns(2, 3). - Concatenation:
(1, 2) + (3, 4)returns(1, 2, 3, 4). - Length:
len((1, 2))returns2. - Membership:
has((10, 20), 10)returnsTrue.
#Map
Key-value pairs, enclosed in curly braces. A value can be any type; a key must be hashable:
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:
m := {[1, 2]: "v"} // error[E0035]: ARRAY is not hashableType 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:
m := {"a": 1, "b": 2} m["a"] -
Index Assignment: Set or update a key directly using bracket syntax:
m <map> m["name"] = "Oxigen" m["version"] = "0.1.0" m["version"] = "0.2.0" // updates existing keyBoth
=and:=work for index assignment. -
Dot Access (string keys): Read and write string-keyed entries with
.namesyntax:y <map> := {"name": "oxigen"} y.name // "oxigen" (same as y["name"]) y.version = "0.1" // inserts the keyDot 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:
y.mising // error: key 'mising' not found on mapBracket access is a lookup and still answers
Nonefor an absent key, so use it (orhas) whenever a key may legitimately be missing. Writing through the dot form still inserts:y["mising"] // None has(y, "mising") // False y.author = "ada" // inserts, no error -
Insert: Adds the key-value pair, or updates it, in place.
extendcopies in every entry of another map:m := {"a": 1} insert(m, "b", 2) // {a: 1, b: 2} extend(m, {"c": 3}) // {a: 1, b: 2, c: 3} -
Remove:
removedeletes a key in place;popdeletes it and returns its value (Noneif it wasn't there);clearempties the map:m := {"a": 1, "b": 2} remove(m, "a") // {b: 2} pop(m, "b") // 2; m is {} -
Keys / Values:
keys({"a": 1, "b": 2}) values({"a": 1, "b": 2}) -
Length:
len({"a": 1})returns1. -
Membership:
has({"a": 1}, "a")returnsTrue(checks keys). -
Copy:
copy(m)makes a separate map;n := mshares one.
#Set
A collection of unique values. Created with the set() built-in, from values
or from an array:
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 above.
#Set Operations
- Add:
push(s, x)orinsert(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)returnsTrue. - Length:
len(set(1, 2, 3))returns3. - Copy:
copy(s)makes a separate set.
All of these change the set in place:
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 module:
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:
b := byte(65)Type keyword: byte. Zero value: 0.
Byte values can be created from integers (0–255), unsigned integers, and characters:
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:
u := uint(42)Type keyword: uint. Zero value: 0.
Uint values can be created from non-negative integers, floats (truncated), strings (parsed), and bytes:
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.
#Vectors
vec2, vec3 and vec4 hold two, three or four numbers: positions,
directions, velocities and colours. Build one from ints or floats:
position := vec3(0, 1.5, -2)
velocity := vec3(1, 0, 0)Read components with .x, .y, .z and .w. They come back as floats:
position.y // 1.5Vectors are values, like numbers: operators make new vectors, and a component cannot be assigned. Build a new vector instead:
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.
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 for the full reference.
enum Stoplight { Red: 0, Yellow: 1, Green: 2 }
light <Stoplight> := Stoplight.Green
light.value // 2Variants 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:
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:
struct Person { name <str> }
p := Person("Alice")
type(p)Use is_type() to check a value's type directly against a type keyword:
is_type(42, int)
is_type("hello", str)
is_type(3.14, float)See also:
- Variables and Assignments — how to declare variables of these types
- Type System — type annotations, conversions, and mutability
- Operators — operations supported by each type
- Built-in Functions — all functions for working with these types