O Language

Using O Language

A practical guide to writing O Language programs and using its language and system features.

O Language pagesPractical guide
On this page

The project describes O Language as a System Oriented Functional Programming Language (SOFPL). In the current runtime, that means functions and collection-based data alongside APIs for files, processes, environment variables, sockets, HTTP servers and databases. This guide starts with a program you can read from top to bottom, then maps the rest of the language to the relevant references.

The examples follow runtime revision eb2301c, matching the OnixOS package snapshot 20260825.898_eb2301c. They describe the interpreter. The bytecode VM and experimental ELF compiler support smaller feature sets; see execution paths.

Install and run a program

On OnixOS, install the olang package when it is available from your configured repositories:

sudo pacman -S olang

Save source as a plain text file with the usual .ola extension. The extension is a convention; the interpreter reads the file contents.

output("Hello from O Language")

Run it from the directory containing the file:

olang hello.ola

You can also use olang run hello.ola. Starting olang without a file opens the interactive REPL. Enter expressions there; help lists the token and built-in names in that runtime, version prints version information, and exit leaves the REPL. See installation and the CLI reference for other ways to install and invoke it.

A small program, end to end

This program stores profile data in an array of hashes, defines a function, iterates through the array and writes one line per profile:

def describe = fn(profile) {
    return profile["name"] + " - " + profile["kind"]
}

def profiles = [
    {"name": "Core", "kind": "CLI"},
    {"name": "KDE", "kind": "Desktop"}
]

for (profiles in index, profile) {
    output(itostr(index) + ": " + describe(profile))
}

Save it as profiles.ola and run olang profiles.ola. The array keeps the rows in order. Each row is a hash; square brackets select a field. fn declares a function, return sends its result to the caller, and the for form binds the array index and current value to index and profile. itostr converts the integer index before string concatenation. output writes the resulting text and a newline.

Expected output:

0: Core - CLI
1: KDE - Desktop

Values, names and expressions

Use def name = expression to bind a value. The parser does not treat a bare assignment such as name = “Core” as a declaration.

Value Example Notes
Integer def count = 6 Whole-number arithmetic uses +, -, * and /.
Float def ratio = float 3.14 The current literal form uses the float keyword; a bare 3.14 is not the documented form.
Boolean def enabled = true The literals are true and false.
String def name = “OnixOS” Double-quoted text; + joins two strings.
Array def names = [“Core”, “KDE”] Ordered values, read with a zero-based integer index.
Hash def profile = {“name”: “Core”} Key/value collection, read with square brackets. String, integer and boolean keys are supported by the interpreter.
Function def identity = fn(value) { return value } Functions can be stored in a name, passed to built-ins and called with parentheses.

The runtime also uses values such as NULL, EMPTY, ERROR, process, socket, database and model objects. Most of those are returned by expressions or built-ins rather than written as source literals. Use type(value) to inspect a value’s runtime type.

Functions and control flow

Functions use fn(parameters) { … }. A function returns a value with return. Pass arguments in the same order as the parameter list:

def total = fn(left, right) {
    return left + right
}

output(itostr(total(5, 3)))

An if condition uses parentheses and a brace-delimited block. An optional else block handles the other branch:

def profile = "KDE"

if (profile == "KDE") {
    output("Desktop profile")
} else {
    output("Another profile")
}

The collection form of for works with arrays and hashes:

def packages = ["pacman", "onix-base"]

for (packages in index, packageName) {
    output(itostr(index) + ": " + packageName)
}

For an array, the first loop name receives the zero-based index and the second receives the element. For a hash, they receive the key and value. Those names are written into the current environment; the loop does not create a local scope. while and loop are also parsed by this revision, but their evaluator behavior is less conventional. Prefer the collection form above unless you have checked the exact loop implementation you are using.

The language supports integer and float arithmetic, numeric comparisons, equality checks, string concatenation, prefix ! and unary minus. It does not implement &&, ||, modulo, dot-property access or compound assignment as normal expressions. Integer and float operands are not automatically converted to each other.

Collections and common built-ins

Arrays and hashes are the main built-in data structures. Use square brackets for both reads:

def systems = ["OnixOS", "Arch Linux"]
def project = {"name": "OnixOS", "base": "Arch Linux"}

output(systems[0])
output(project["base"])

Common collection helpers:

Function Use
len(value) Get a string, array or integer’s length.
first(array), last(array), rest(array) Read the first or last value, or return the array without its first value.
push(array, value) Return a new array with one value appended. The hash form is push(hash, key, value).
map(function, array) Call a function for each supported element and collect results in a new array.
itostr(integer), atostr(array[, separator]) Convert an integer to text or join array values. atostr currently appends the separator after the final value too.
inspect(values…), type(value) Print runtime representations or inspect a value’s type.

The runtime API reference lists the full built-in set, expected arguments, return values and known implementation limits.

Working with files and the host system

O Language built-ins can read and write files, inspect paths, work with environment variables, launch commands and access sockets. Paths are resolved by the host process, usually relative to the directory from which olang was started.

For example, read a JSON settings file and select a field:

def settings = jsonf("settings.json")
output(settings["mode"])

The env statement sets a process environment value; sysenv reads it back:

env "ONIX_MODE" "development"
output(sysenv("ONIX_MODE"))

The proc statement runs a command and binds a process result. In this runtime it waits for the command to finish and captures combined output:

proc platform "uname -s"
output(proc_out(platform)[0])

File APIs include read, write, remove, mkdir, rmdir, finfo, chmod and exist. Process APIs include exec, sh_exec, cmd_exec, run, proc_out, proc_pid and proc_kill. These calls act on the host and are not sandboxed; review a program before running it. See the runtime API reference for details.

Networking, databases and web applications

The runtime also includes APIs for several system-oriented tasks. Their signatures and support differ by backend, so use the dedicated references before building on them.

Area Main forms Documentation
TCP and UDP sockets sock, sock_send, sock_listen TCP example and runtime API
HTTP server webserver(port, routes) and route hashes Web project guide
Database access database, model, migrate, fetch, create, update, delete, query Database built-ins
Data formats json, jsonp, jsonf, xmlp, xmlf Runtime API
Text and math Regular expressions, string replacement, random values, math helpers and terminal color Runtime API

The database API supports the postgres, mysql, sqlite3 and internal connection types in the reviewed interpreter. Not every operation works with every backend: for example, search and the file-backed internal store have separate limits. Check the API reference before choosing a backend.

Send a message over TCP

The socket listener blocks while it serves requests, so run the server and client in separate terminals. Save this as server.ola:

sock echoServer "tcp" "9050" "127.0.0.1"
sock_listen(echoServer, {"ping": "pong"})

Start it with olang server.ola. In another terminal, run this client:

sock client "tcp" "9050" "127.0.0.1"
output(sock_send(client, "ping"))

The server binds only to loopback in this example. The current socket helpers use simple message-to-response matching and do not provide a general-purpose protocol or connection lifecycle API.

Create and query a SQLite model

The SQL backends use the database and model objects. A minimal SQLite example is:

def db = database("sqlite3", "profiles.db")
def profiles = model(db, "profiles", {"name": "TEXT", "desktop": "TEXT"})

migrate(profiles)
create(profiles, {"name": "KDE", "desktop": "Plasma"})

def rows = fetch(profiles, {"name": "KDE"})
inspect(rows)

migrate creates the SQL table from the model columns. create inserts the fields in a hash, and fetch returns matching rows. This runtime revision imports SQLite, MySQL and PostgreSQL drivers. The internal backend has different behavior and does not implement migrations or raw SQL; the API reference documents the current backend boundaries.

Start a small HTTP route

The built-in server can serve a route whose response is a string:

def routes = {
    "/": {"type": "GET", "response": "Hello from O Language"}
}

webserver("10000", routes)

This binds a server to the selected port and keeps the process serving requests. Project scaffolding, route generation, static folders and upload options are covered in web projects.

Source files, modules and project files

File or form What it is used for
.ola Common extension for O Language source files. Run one directly with olang file.ola.
load “path/file.ola” Parse another source file and evaluate its statements in the current environment. The path is relative to the process working directory.
library.olp JSON project/library manifest used by the dependency commands. It is not a source module or a build script.
proc.ops INI-style process configuration used by the process and web commands.

The old Wiki lists additional file extensions, but the current runtime’s common source and project workflows use the entries above. Do not assume an extension listed in historical documentation is supported by a current CLI command.

Load a source module

Keep source files in a project directory and load them from the process working directory. For example:

project/
  main.ola
  lib/profile.ola

lib/profile.ola:

def profile_name = fn() {
    return "OnixOS"
}

main.ola:

load "lib/profile.ola"
output(profile_name())

Run the program with the working directory set to project/. load evaluates the module’s statements in the current environment; it is not a namespaced import system. See syntax and file loading for path behavior and limits.

Use olang generate, olang install and olang get for the manifest workflows described in project files. Dependencies are Git repositories loaded by the application; review them before using them. The manifest’s main, compiled, watch and install fields do not currently make the CLI build or install an application.

Language keywords and operators

The everyday source forms are:

Form Purpose
def, fn, return Bind values, define functions and return from them.
if, else, for, in Branch and iterate over a collection.
true, false, float Boolean and float literal syntax.
load Load a source file from the current working directory.
env, proc, sock Set an environment value, run a host process or define a socket.

The parser also recognizes more specialized forms such as scope/set, route, signal, begin/except/recover/final, async/wait and eval. They have uneven or experimental behavior across the interpreter and compiler paths; do not treat a reserved word as a stable feature without checking its implementation.

The lexer treats # as end-of-input, so it cannot be used for comments in a runnable file. Older Wiki and repository examples also show forms such as bare assignment, colon-indented blocks, println, show, && and ||; those do not match the current parser or built-in table.

Where to go next