My toy programming language, parser and compiler to play around with different ideas for a perfect (IMO) language.
Orlang compiles to self-contained native binaries via LLVM. The only
external requirement is clang on your PATH — the garbage collector and
the built-in map runtime are embedded in the compiler and linked into every
program automatically.
go install github.com/orktes/orlang # build the compiler
orlang build main.or # produces ./main
orlang run main.or # compile and run in one step
orlang build main.or --target llvm # emit LLVM IR only (main.ll)
orlang build main.or --target js # compile to JavaScriptA minimal program needs no declarations at all:
fn main() {
println("hello", "world", 42, 1.5, true)
}
- Static typing with implicit safe numeric widening (
var x: int64 = 5,var f: float64 = 1.5, mixed-width arithmetic) constdeclarations with enforced immutability- Structs with methods, interfaces, enums, tuples, closures and first-class functions, operator overloading
- Built-in maps: literals, indexing,
len,contains,delete, andfor var key, value in miteration - Slices and fixed arrays with
len/append, string concatenation and indexing print/println/strbuiltins; C interop viaextern,include "header.h", andlinkdirectives- Numeric literals: hex
0xFF, binary0b1010, octal0o17, digit separators1_000_000, float exponents2.5e-3 - Conservative mark-and-sweep garbage collection (set
ORLANG_GC_STRESS=1to collect before every allocation when hunting GC bugs)
Programs import standard library modules straight from the compiler binary — no files to install, and executables stay fully self-contained:
import { server, Request, Response } from "std/http.or"
import { parse, object, text, number, Json } from "std/json.or"
fn main() {
var app = server()
app.get("/", fn (req: Request, res: Response) => void {
res.send("Hello, world!")
})
app.post("/echo", fn (req: Request, res: Response) => void {
var body = parse(req.body())
var out = object()
out.set("you_sent", body.get("message"))
res.json(out.stringify())
})
app.listen(8080)
}
std/http.or— Express-style HTTP server:get/post/put/delete/allrouting, request method/path/query/headers/body, response status/headers/send/json. Implemented on POSIX sockets in the embedded runtime; no external libraries.std/json.or— JSON parsing and building:parse(s).get("key").at(0).str()with safe chaining on missing values,object()/array()/text()/number()/boolean()builders, andstringify().
See examples/http_server for a complete JSON todo API.
Orlang has Go-style concurrency built in — cooperative green threads with CSP channels, scheduled entirely inside the embedded runtime:
fn worker(id: int32, jobs: chan int32, results: chan int32) {
for {
var job = recv(jobs)
if closed(jobs) && job == 0 {
return
}
send(results, job * 10 + id)
}
}
fn main() {
var jobs: chan int32 = channel(0) // capacity 0 = rendezvous
var results: chan int32 = channel(8) // buffered
go worker(1, jobs, results) // spawn a green thread
send(jobs, 5)
println(recv(results)) // 51
close(jobs)
}
go f(args...)spawns a green thread; arguments are evaluated at spawn time. Works with named functions, function-typed variables, and immediately-invoked lambdas (go fn () => void { ... }()).channel(cap)creates achan T;send/recvpark the calling thread instead of blocking the process,close/closedgive Go-like termination semantics,yield()cedes the processor explicitly.- All standard library IO cooperates: a blocked read, write, or accept
parks its thread and the scheduler polls, so
std/httpserves each connection on its own green thread andstd/netservers and clients can interleave in a single process. - Task stacks are GC-allocated and traced through the scheduler, so the collector sees objects referenced only from sleeping threads.
std/net.or— TCP:listen(port),dial(host, port), andConnread/write/close.
Scheduling is cooperative and single-threaded: switches happen at
channel operations, IO, and yield() — there is no preemption and no
parallelism (yet).
select waits on several channels at once, with optional default:
select {
case var job = recv(jobs) {
println("job:", job)
}
case send(results, 42) {
println("delivered")
}
default {
println("nothing ready")
}
}
Exactly one ready case runs (in-order preference when several are ready);
with no default, the thread parks until a case can fire. Closed channels
count as ready and receive zero values, and case recv(ch) without a
binding discards the received value.
typeof(expr)— the static type of an expression as a string (int32,map[string]int32,chan int32,Point). The argument is not evaluated.typename(v)— the dynamic type name of an interface value, looked up through its itable at runtime; on non-interface values it equalstypeof.v is Type/v as Type— runtime type test and cast for interface values, including user-defined struct types.
go test ./... # compiler unit tests
cd e2e && ./run.sh # end-to-end tests (build + run every program)