Strict by default
Types are explicit and validated early. You get fewer hidden conversions and clearer diagnostics.
Simple Project
A strict language and runtime stack focused on predictable behavior, explicit boundaries, and an inspectable pipeline.
Simple is built around one clear execution story: source code lowers to a readable intermediate form, then to bytecode, then executes in a verified VM. The point is not clever syntax tricks. The point is control: deterministic behavior, clear failure modes, and an architecture you can embed and reason about.
Types are explicit and validated early. You get fewer hidden conversions and clearer diagnostics.
You can inspect SIR and SBC artifacts directly, making compiler/runtime debugging practical.
The VM is designed as a host boundary for app scripting, tools, and sandboxed execution.
Strict typing, explicit mutability, artifacts, enums, modules, and script-style top-level execution.
.simple -> SIR -> SBC -> VM execution with loader/verifier gates and deterministic diagnostics.
Interpreter execution, heap/GC, arrays/lists/strings, and core system module dispatch.
DL-based C/C++ interop with strict extern signatures and ABI marshalling.
Docs/Lang.md - syntax, semantics, and validation.
Docs/StdLib.md - reserved imports and APIs.
Docs/IR.md and Docs/Byte.md.
Variables, parameters, and returns are typed. Type mismatches fail at validation.
count : i32 = 42
label :: string = "ready"
add : i32 (a : i32, b : i32) {
return a + b
}
Strict typing is enforced across variables, parameters, and returns.
: is mutable. :: is immutable.
x : i32 = 10
name :: string = "Simple"
x = x + 1 # ok
name = "Next" # compile error
The same rules apply to artifact fields.
Top-level statements execute in source order via implicit __script_entry. Function declarations do not execute implicitly.
import io
io.println("boot")
sum : i32 (a : i32, b : i32) {
return a + b
}
io.println(sum(2, 3))
An explicit main is used only when no top-level statements exist.
Artifact defines structured layouts with fields and optional methods. Enum is scoped and strongly typed. Module exposes constants.
Status :: Enum { Idle = 0, Running = 1 }
Player :: Artifact {
hp : i32
damage : void (amount : i32) { self.hp -= amount }
}
Config :: Module {
MAX_PLAYERS :: i32 = 16
}
These map cleanly through validation and IR lowering.
Reserved modules use keyword-style paths. Local modules use quoted paths.
import io
import dl
import fs
import "raylib"
import "./raylib.simple"
Reserved imports map to runtime namespaces defined in Docs/StdLib.md.
extern declares native signatures, and DL handles ABI marshaling.
import dl
import io
extern raylib.InitWindow : void (w: i32, h: i32, title: string)
extern raylib.CloseWindow : void ()
lib: i64 = dl.open("raylib.dll", raylib)
if (lib == 0) {
io.println("raylib load failed: {}", dl.last_error())
}
Signatures are strict; unsupported ABI shapes are rejected.
# compile + run source
simple run Tests/simple/hello.simple
# inspect IR
simple emit -ir Tests/simple/math.simple --out math.sir
# build bytecode artifact
simple emit -sbc Tests/simple/math.simple --out math.sbc
# execute bytecode directly
simplevm run math.sbc
Compare the same intent across source, IR, and bytecode layers.
High-level typed source with explicit mutability/import boundaries.
import io
sum: i32 (a: i32, b: i32) {
return a + b
}
total: i32 = sum(10, 32)
io.println("total={}", total)
Typed intermediate instructions that are easy to inspect and validate.
; representative SIR
fn sum(a:i32, b:i32) -> i32 {
%0 = add.i32 a, b
ret %0
}
fn __script_entry() -> i32 {
%1 = const.i32 10
%2 = const.i32 32
%3 = call sum(%1, %2)
call core.io.println_fmt("total={}", %3)
ret 0
}
Common IR codes: const.*, add.i32, call, jmp, ret, load.local, store.local.
Verified bytecode instructions executed by the VM interpreter/JIT path.
; representative SBC opcodes
CONST_I32 10
CONST_I32 32
CALL sum
STORE_LOCAL 0
LOAD_LOCAL 0
IMPORT_CALL core.io.println_fmt
RET_I32 0
Frequently used VM opcodes: CONST_I32, ADD_I32, SUB_I32, MUL_I32, DIV_I32, CALL, CALL_INDIRECT, JMP, JMP_TRUE, JMP_FALSE, RET.
The VM is split into explicit subsystems so behavior remains auditable and testable.
Parses SBC sections, metadata, constants, and signatures into a runtime module representation.
Rejects malformed bytecode and invalid type/control-flow combinations before any execution starts.
Executes opcodes with strict checks and deterministic semantics. Traps are explicit and testable.
System modules expose controlled host services for IO, files, math, OS, and dynamic loading.
Host apps can compile/load/run modules and control runtime lifecycle for plugin or script scenarios.
Tiering exists; optimization is ongoing. Baseline correctness and coverage still take priority.
Simple keeps system features modular to avoid overlap and make dependency intent explicit.
| Function | Purpose |
|---|---|
buffer_new(length) | Create a new i32 buffer/list of requested length. |
buffer_len(buffer) | Return current buffer length. |
buffer_fill(buffer, value, count) | Fill up to count slots with value; returns written count. |
buffer_copy(dst, src, count) | Copy up to count elements from source to destination. |
print(value) / println(value) | Stream output for scalar/format-based text emission. |
| Function | Purpose |
|---|---|
open(path, mode) | Open file and return handle/id for follow-up operations. |
read(handle, buffer, count) | Read bytes/values into provided buffer; returns read count. |
write(handle, buffer, count) | Write bytes/values from buffer; returns written count. |
close(handle) | Close file handle and release underlying resource. |
| Function | Purpose |
|---|---|
sqrt(x) | Square root for numeric values. |
abs(x) | Absolute value helper. |
pow(x, y) | Power/exponent helper. |
now() | Retrieve current time/tick value for timing logic. |
| Function / Value | Purpose |
|---|---|
supported / has_dl | Capability flags for dynamic loading support. |
is_linux / is_macos / is_windows | Platform feature flags for branching behavior safely. |
open(path) | Open dynamic library and return handle. |
sym(handle, name) | Resolve symbol pointer from a dynamic library. |
close(handle) | Close dynamic library handle. |
last_error() | Return diagnostic message from latest DL operation. |
run, check, build, compile, emit, lsp
.simple, .sir, .sbc (VM path)
simple --version, simple -v, simple version
./build_linux, ./build_macos, ./build_windows
Use extern to declare native symbols and call them through DL.
import dl
import io
extern raylib.InitWindow : void (w: i32, h: i32, title: string)
extern raylib.CloseWindow : void ()
lib: i64 = dl.open("raylib.dll", raylib)
if (lib == 0) {
io.println("raylib load failed: {}", dl.last_error())
}
raylib.InitWindow(800, 450, "Simple + raylib")
raylib.CloseWindow()
Top-level statements execute in order.
import io
io.println("hello")
io.println(10 + 20)
Artifacts give explicit member layout and typed access.
Point :: Artifact { x: i32 y: i32 }
sum: i32 (p: Point) {
return p.x + p.y
}
Use the same program through each stage of the stack.
simple emit -ir app.simple --out app.sir
simple emit -sbc app.simple --out app.sbc
simplevm run app.sbc
git clone https://github.com/JJLDonley/Simple
cd Simple
./build.sh --suite all
./bin/simple run Tests/simple/hello.simple
Releases: github.com/JJLDonley/Simple/releases