Simple Language Documentation
Simple
Identity-first, strictly typed — scripted in minutes, portable everywhere.
Simple compiles .simple source into SIR, then portable SBC bytecode for the Simple VM. CLI runs use JIT by default when LLVM ORC support is available. The language is designed for explicit types, direct control flow, readable compiler output, canonical libraries, and native-backed programs.
module examples.hello
import Standard.IO
main :: i32 () {
Standard.IO.println("hello from Simple")
return 0
}
| Layer | Status | Purpose |
|---|---|---|
| Language | Implemented | Strict typed source syntax for programs and scripts. |
| SIR | Partial | Readable intermediate representation used to inspect lowering. |
| SBC | Partial | Compact bytecode format loaded and verified by the runtime. |
| VM | Partial | Execution engine, heap, native dispatch, traps, and runtime limits. |
Why Simple
Simple came about from wanting an identity-first, strictly typed language that scripts quickly and ships as portable VM bytecode.
Simple keeps data, functions, namespaces, artifacts, modules, and native APIs as direct language tools. Use the shape that fits the program.
Design goals
- General-purpose programming: useful for tools, applications, scripts, and native-backed programs.
- Identity-first model: data, functions, artifacts, modules, and libraries keep explicit names and boundaries.
- Static typing: reject type mistakes before execution.
- Quick scripting: top-level statements are valid when a full application structure is unnecessary.
- Robust native library: low-level
System.*and high-levelStandard.*APIs.
Installation
Status: Implemented
Download a packaged interpreter or LLVM build from the latest release, or build from source. The developer CLI is svm; simple is the runtime stub for generated programs.
Build
# Linux / macOS
./scripts/build/local.sh
# Windows
scripts\build\windows.bat
Install
The Unix installer enables LLVM 18 JIT by default. Use --no-llvm-jit for an interpreter-only installation.
# Linux / macOS: LLVM JIT enabled by default
./scripts/install/unix.sh
# interpreter-only install
./scripts/install/unix.sh --no-llvm-jit
# custom binary directory
BINDIR="$HOME/.local/bin" ./scripts/install/unix.sh
| Binary | Role |
|---|---|
svm | Compiler, checker, emitter, runner, build tooling. |
simple | Runtime stub only; not the compiler command. |
Getting started
Status: Implemented
Create hello.simple:
module hello
import Standard.IO
main :: i32 () {
Standard.IO.println("hello from Simple")
return 0
}
Run, check, or emit bytecode:
svm run hello.simple
svm check hello.simple
svm emit -sbc hello.simple --out hello.sbc
Top-level statements are also valid for named script-style files:
module hello.script
import Standard.IO
Standard.IO.println("script start")
add :: i32 (a : i32, b : i32) {
return a + b
}
Standard.IO.println("sum={}", add(2, 3))
Program structure
Status: Implemented
Every Simple source file begins with a qualified module name, followed by imports, declarations, and optional top-level statements. Script files require module names too and execute top-level statements in source order through an implicit script entry.
module app.main
import Standard.IO
import Standard.Math
version :: string = "0.5"
main :: i32 () {
Standard.IO.println(version)
return 0
}
| Construct | Meaning |
|---|---|
module app.main | Declares the required runtime module namespace and named-import identity. |
import Standard.IO | Canonical high-level library import. |
import Standard.Math | Canonical standard math module import. |
main :: i32 () | Immutable entry-point function when no top-level script body is used. |
Declarations
Status: Implemented
Simple separates mutability from type. : declares mutable bindings. :: declares immutable bindings, functions, methods, and named constructs.
count : i32 = 1
name :: string = "Simple"
count = count + 1
// name = "Other" // rejected: immutable
Functions
add :: i32 (a : i32, b : i32) {
return a + b
}
log :: void (message : string) {
Standard.IO.println(message)
}
Types
Status: Partial
Types are explicit in declarations, function parameters, returns, arrays, lists, artifacts, and native-facing APIs.
Primitive types
| Group | Types |
|---|---|
| Unit | void |
| Boolean | bool |
| Text | char, string |
| Signed integers | i8, i16, i32, i64 |
| Unsigned integers | u8, u16, u32, u64 |
| Floating point | f32, f64 |
Named and procedure types
Point :: artifact { x : i32; y : i32 }
Color :: enum { Red, Green }
p : Point = { .x = 3, .y = 4 }
c : Color = Color.Green
op : i32 (i32, i32) = add
Literals
Status: Implemented
Literals are checked against the expected type where needed.
b :: bool = true
letter :: char = 'x'
name :: string = "Simple"
decimal :: i32 = 42
hex :: i32 = 0x10
binary :: i32 = 0b1010
ratio :: f64 = 3.14
array :: i32{3} = {1, 2, 3}
list :: i32[] = [1, 2, 3]
point : Point = { .x = 1, .y = 2 }
Expressions
Status: Implemented
Arithmetic, comparison, assignment, calls, member access, casts, and indexing are type checked.
x : i32 = 10
y : i32 = 20
sum : i32 = x + y
same : bool = x == y
narrow : i32 = @i32(3.0)
first : i32 = values[0]
result : i32 = add(x, y)
Invalid operations such as boolean arithmetic, non-integer indexes, unknown members, incompatible assignment, and unsupported casts are rejected during validation.
Built-in functions and forms
Status: Implemented where listed
Simple keeps built-ins small and explicit. Library work belongs under System.* and Standard.*; language built-ins cover core operations that need compiler knowledge.
len(value)
Returns the length of a string, fixed array, or list as i32.
name : string = "Simple"
letters : i32 = len(name)
fixed : i32{3} = {1, 2, 3}
fixedCount : i32 = len(fixed)
items : i32[] = [10, 20, 30]
itemCount : i32 = len(items)
Explicit primitive casts: @Type(value)
Primitive conversions must be requested explicitly. Calls like i32(value) are rejected; use @i32(value). This is how Simple enforces no implicit coercion.
wide : i64 = 42
narrow : i32 = @i32(wide)
ratio : f64 = 3.5
whole : i32 = @i32(ratio)
text : string = "42"
parsed : i32 = @i32(text)
List methods
Growable lists expose checked methods for common mutations and queries.
items : i32[] = []
items.push(10)
items.push(20)
count : i32 = items.len()
last : i32 = items.pop()
items.insert(0, 5)
removed : i32 = items.remove(0)
items.clear()
Standard output helpers
Printing is library-provided, not a global builtin. Use Standard.IO for output and format strings.
import Standard.IO
Standard.IO.print("answer={}", 42)
Standard.IO.println(" done")
Control flow
Status: Implemented
Blocks create scopes. if, loops, break, skip, return, and switch-style forms are checked by the validator.
If statements
status : string = ""
if (score >= 70) {
status = "pass"
} else {
status = "retry"
}
Condition chains
scoreLabel :: string (score : i32) {
|> (score >= 90) { return "great" }
|> (score >= 70) { return "solid" }
|> default { return "needs work" }
}
Switch expressions
dayName :: string (day : i32) {
return switch (day) {
day == 1 => "mon"
day == 2 => "tue"
day == 3 => "wed"
default => "other"
}
}
For loops
total : i32 = 0
for (i : i32 = 0; i < 10; i = i + 1) {
if (i == 5) { skip }
total = total + i
}
Range loops
total : i32 = 0
for n : i32 = 1 .. 4 {
total = total + n
}
While loops, break, and return
module examples.loops
main :: i32 () {
total : i32 = 30
while (total > 20) {
total = total - 1
if (total == 25) { break }
}
return total
}
Arrays and lists
Status: Partial
Fixed arrays use T{N}. Growable lists use T[]. Indexes must be integers and element types must match.
fixed : i32{3} = {1, 2, 3}
items : i32[] = [1, 2, 3]
fixedFirst : i32 = fixed[0]
items[1] = 9
count : i32 = len(items)
Artifacts
Status: Implemented
Artifacts define structured data with optional methods. Fields define layout. Methods define behavior and do not change ABI layout.
Counter :: artifact {
value : i32
inc :: void () {
self.value = self.value + 1
}
get :: i32 () {
return self.value
}
}
c : Counter = { .value = 0 }
c.inc()
Standard.IO.println("{}", c.get())
Inside methods, artifact fields are accessed through self.
Enums
Status: Implemented
Enum variants are scoped under the enum type. Use qualified access. Explicit numeric values are optional.
Color :: enum {
Red
Green
Blue
}
favorite : Color = Color.Green
Namespaces
Status: Implemented
Each source file belongs to its declared module namespace. A namespace declaration adds an explicitly named group for constants, utility functions, and domain APIs.
Maths :: namespace {
PI :: f64 = 3.14159
add :: i32 (a : i32, b : i32) {
return a + b
}
}
value : i32 = Maths.add(2, 3)
Namespace members are accessed through the namespace name. This keeps APIs grouped while preserving Simple's direct function/data model.
Modules and imports
Status: Partial
Every source file declares a runtime module namespace. Unquoted imports resolve module names through module headers or module maps; quoted imports resolve explicit source paths. Import resolution also handles reserved modules, relative paths, missing imports, ambiguity, and cycles.
module examples.imports
import Standard.IO
import System.IO
import app.config
import "./local_helpers"
import "./legacy_helpers.simple"
| Import form | Purpose |
|---|---|
import Standard.IO | Canonical high-level library module import. |
import System.IO | Canonical low-level system module import. |
import app.config | Named project module import resolved by module indexing. |
import "./local_helpers" | Explicit relative source import; the .simple extension is optional. |
FFI and extern
Status: Partial
Simple exposes native interop through strict extern declarations and dynamic-library runtime APIs. Extern signatures are checked; unsupported ABI shapes are rejected instead of guessed.
module examples.ffi
import System.FFI
import Standard.IO
extern raylib.InitWindow : void (w : i32, h : i32, title : string)
extern raylib.CloseWindow : void ()
lib :: i64 = System.FFI.open("libraylib.so", raylib)
if (lib == 0) {
Standard.IO.println("raylib load failed")
}
lib.InitWindow(800, 600, "Simple")
lib.CloseWindow()
See the locally runnable Raylib platformer source and its Raylib bindings.
ABI rules
- Extern declarations require explicit parameter and return types.
- Procedure values are rejected at extern ABI boundaries.
- Unsupported ABI types are rejected during validation.
- Recursive artifact ABI is rejected.
- Artifacts are treated as structs for ABI layout: fields define layout; methods do not.
The low-level foreign-function surface is canonicalized as System.FFI. Short imports such as DL are rejected with migration diagnostics, not treated as aliases.
Native library: System | Standard
Status: Planned
Simple has a robust native library direction for both low-level and high-level coding. The model is layered so high-level convenience does not bypass low-level ownership, capabilities, or resource safety.
Layer model
| Layer | Role |
|---|---|
| VM Native Core | Internal resource registry, native dispatch metadata, handle validation, cleanup, and capability checks. |
System.* | Low-level explicit APIs close to host/runtime behavior. |
Standard.* | High-level APIs built over System.* for normal application code. |
| Legacy diagnostics | Short imports are rejected with canonical System.* / Standard.* replacements; they are not aliases. |
Core native-facing types
| Type | Status | Purpose |
|---|---|---|
System.Handle<T> | Planned | Typed opaque native resource handle. Resource kind is preserved and checked before use. |
Result<T> | Planned | Expected success/failure result. Used for IO, OS, network, FFI, and native operations. |
Option<T> | Planned | Presence/absence or ready/not-ready state without sentinel values. |
Promise<T> | Planned | Planned async result carrier. await() returns Result<T>; poll() returns Option<Result<T>>. |
System modules
System.* is explicit and close to host/runtime behavior. It exposes typed handles, explicit cleanup, capability checks, blocking behavior, and explicit error values.
| Area | Status | Purpose |
|---|---|---|
System.FS | Partial | Open, close, read, write, seek, stat, list directories, create/remove/copy/rename files. |
System.Path | Partial | Join, normalize, absolute/relative paths, basename, dirname, extension, separators. |
System.Buffer | Partial | Native buffer handles, allocation, close, length, slicing, copying, and bounds-checked access. |
System.Bytes | Planned | Heap-owned byte sequences, byte/string conversion, endian reads/writes, and safe byte operations. |
System.Net | Planned | TCP/UDP sockets, listeners, connect, listen, accept, send, receive, close. |
System.HTTP | Planned | HTTP client requests, HTTP server handles, request parsing, response writing, limits, and timeouts. |
System.Terminal | Planned | Raw mode, alternate screen, cursor control, terminal size, styles, key/mouse/resize events, cleanup on exit. |
System.Process | Planned | Process spawn, wait, kill, stdin/stdout/stderr handling, and process capability checks. |
System.Thread | Partial | OS thread handles, sleep, yield, hardware concurrency, and thread-related runtime operations. |
System.Job | Planned | VM job handles, spawn, join, detach, cancellation, and structured result propagation. |
System.Channel | Partial | Channel handles, send, trySend, receive, tryReceive, pending, close, and blocked-operation wakeup. |
System.Time | Partial | Wall clock, monotonic clock, timer handles, sleep, and duration/time conversion support. |
System.Random | Partial | Seeded random generator handles and random numeric values. |
System.Json | Partial | JSON parse, stringify, object/array accessors, typed getters, and JSON value handles. |
System.Log | Partial | Log levels, structured messages, runtime sinks, file sinks, and logger resource cleanup. |
System.FFI | Partial | Foreign function interface, dynamic libraries, symbols, strict ABI signatures. |
System.ASM | Planned | C/DynASM native-code units, object generation, symbol lookup, stub embedding, AOT linking. |
Standard modules
Standard.* provides higher-level APIs over System.*. These APIs reduce ceremony while keeping ownership and failure explicit.
| Module | Status | High-level shape |
|---|---|---|
Standard.IO | Implemented | print, println, format variants, and future readLine. |
Standard.FS | Partial | readText, writeText, readBytes, copy, move, walk. |
Standard.Path | Partial | Path joining, normalization, basename, dirname, extension, and platform separators. |
Standard.Buffer | Reserved | High-level mutable/growable/cursor buffer helpers. |
Standard.Bytes | Reserved | Byte construction, UTF-8 conversion, slices, copying, and encoding helpers. |
Standard.Json | Reserved | Future high-level JSON value API. |
Standard.Math | Partial | PI, abs, min, max, sqrt, and future math helpers. |
Standard.HTTP | Planned | get, post, serve, request/response helpers, async client APIs. |
Standard.HTTPS | Planned | TLS-backed server/client helpers with secure defaults and clear certificate diagnostics. |
Standard.Console | Planned | Write, writeLine, readLine, clear, colors, and key helpers. |
Standard.Terminal | Planned | Raw mode, alternate screen helpers, event pump, cursor, screen primitives, and cleanup helpers. |
Standard.Process | Planned | Run commands, collect exit code/stdout/stderr, and async process execution. |
Standard.Time | Reserved | Current time, monotonic time, sleep, timers, and duration helpers. |
Standard.Random | Partial | Random integers, ranges, floating-point values, and seeded RNG wrappers. |
Standard.Log | Partial | Simple logging helpers, levels, structured fields, and high-level sinks. |
Standard.Promise | Planned | run, await, poll, cancel, isDone. |
API conventions
- Sync native APIs return
Result<T>orOption<T>for expected failures and absence. - Async APIs live under lowercase
.async, for exampleStandard.HTTP.async.get(...). - Wrong handle kind, stale handle, invalid API contract, and VM invariants trap.
- There are no public short aliases. Use canonical
System.*andStandard.*imports.
file : Result<System.FS.FileHandle> = System.FS.open("data.txt", mode)
page : Promise<HTTPResponse> = Standard.HTTP.async.get("https://example.com")
ready : Option<Result<HTTPResponse>> = page.poll()
Toolchain
Status: Implemented
The core toolchain is:
- Language
.simplesource. - SIRReadable intermediate representation.
- SBCBytecode artifact.
- VMVerified execution.
svm emit -ir main.simple --out main.sir
svm emit -sbc main.simple --out main.sbc
svm run main.simple
Runtime
Status: Partial
The Simple runtime is the execution environment behind SBC bytecode. It loads bytecode, verifies structure, executes instructions, manages VM-owned values, and provides native-backed runtime modules.
Runtime responsibilities
| Area | Status | Responsibility |
|---|---|---|
| Loader | Implemented | Reads SBC files, validates headers, sections, bytecode version, and entry metadata. |
| Verifier | Partial | Rejects malformed bytecode before execution. Planned work extends this to result/option/promise and typed handle metadata. |
| Interpreter | Implemented | Executes VM instructions, manages call frames, locals, globals, traps, and runtime limits. |
| Heap and GC | Partial | Owns heap values such as strings, arrays, lists, artifacts, and runtime objects. Stack maps and declared roots keep references alive. |
| Native dispatch | Partial | Connects language/runtime calls to canonical modules such as System.IO, System.FS, System.Json, buffers, channels, time, random, logging, and FFI. |
| Runtime limits | Partial | Tracks execution constraints so runaway programs can be limited by the VM. |
Execution model
svmcompiles or loads a program.- The loader reads SBC metadata and bytecode sections.
- The verifier rejects unsupported or malformed bytecode.
- The VM executes verified instructions. CLI runs use LLVM ORC JIT by default when available and fall back safely where unsupported.
- Native calls cross through runtime dispatch instead of direct unchecked host access.
Values and traps
Expected runtime and OS failures should return values such as Result<T> or Option<T>. Traps are reserved for invalid API contracts, wrong handle kind, stale handles, bytecode violations, and VM invariants.
Generated stubs
The simple binary is a runtime stub. It is intended for generated or embedded SBC payloads. Compiler and tooling behavior belongs to svm.
Timeline
Status: Planned
The current project priority is native-library architecture and the compiler/runtime prerequisites needed to support it. Broad cleanup work is lower priority unless it directly supports native-library implementation.
Current order of work
- Compiler / IR / bytecode prerequisitesAdd built-in
System.Handle<T>,Result<T>,Option<T>,Promise<T>, and preserve their metadata through SIR/SBC. - Native ABI contractDefine exact ABI behavior for scalars, artifacts-as-structs, strings, bytes, handles, results, options, promises, and native calls.
- VM Native CoreImplement resource registry, native metadata, handle generation checks, cleanup behavior, and capability policy.
- Low-level
System.*Build explicit host/runtime APIs for FS, path, buffers, FFI, ASM, threads/jobs/channels, net, HTTP, terminal, process, env, time, random, log, and JSON. - High-level
Standard.*Add high-level library APIs overSystem.*without public short aliases. - Docs and testsGenerate API docs, capability tables, lifecycle tables, platform availability, native tests, CLI integration tests, and cross-platform checks.
Lower priority backlog
SRP cleanup, AOT/JIT follow-up, packaging, and broad internal refactors remain important, but they should not displace native-library prerequisites unless the work directly supports the native roadmap.
Status
Simple is in active development. Current surface includes parser, resolver, type checker, canonical System.*/Standard.* imports, artifacts, enums, arrays, lists, strings, SIR/SBC emission, loader/verifier, VM execution, native modules, CLI, LSP, and LLVM ORC JIT.
Current tool version: v0.5.0. Full suite status: 1821/1821, including JIT 61/61.