Skip to content

Repository files navigation

Coverage Status Build Status GoDoc

orlang

My toy programming language, parser and compiler to play around with different ideas for a perfect (IMO) language.

Try Orlang

screenshot 2017-09-10 23 49 39

Building native executables

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 JavaScript

A minimal program needs no declarations at all:

fn main() {
    println("hello", "world", 42, 1.5, true)
}

Language highlights

  • Static typing with implicit safe numeric widening (var x: int64 = 5, var f: float64 = 1.5, mixed-width arithmetic)
  • const declarations with enforced immutability
  • Structs with methods, interfaces, enums, tuples, closures and first-class functions, operator overloading
  • Built-in maps: literals, indexing, len, contains, delete, and for var key, value in m iteration
  • Slices and fixed arrays with len/append, string concatenation and indexing
  • print/println/str builtins; C interop via extern, include "header.h", and link directives
  • Numeric literals: hex 0xFF, binary 0b1010, octal 0o17, digit separators 1_000_000, float exponents 2.5e-3
  • Conservative mark-and-sweep garbage collection (set ORLANG_GC_STRESS=1 to collect before every allocation when hunting GC bugs)

Standard library

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/all routing, 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, and stringify().

See examples/http_server for a complete JSON todo API.

Concurrency: green threads and channels

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 a chan T; send/recv park the calling thread instead of blocking the process, close/closed give 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/http serves each connection on its own green thread and std/net servers 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), and Conn read/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.

Reflection

  • 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 equals typeof.
  • v is Type / v as Type — runtime type test and cast for interface values, including user-defined struct types.

Testing

go test ./...        # compiler unit tests
cd e2e && ./run.sh   # end-to-end tests (build + run every program)

About

My toy programming language, parser and compiler to play around with different ideas for a perfect (IMO) language

Resources

Stars

10 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages