Obiwan Language Documentation

A dynamically-typed scripting language with async/await, closures, destructuring, and a rich standard library.

dynamic typing async / await first-class fns closures try / catch promise chaining classes .obi files

🌐 Overview

Language Highlights
// Obiwan — a scripting language with modern ergonomics

// First-class functions
fn greet(name) {
    println("Hello, " + name + "!");
}

// Async / await
fn fetchData() async {
    var result = await someAsyncOp();
    return result;
}

// Closures and higher-order functions
const double = fn(x) { return x * 2; };

// Destructuring
var [a, b] = [1, 2];
var { name, age } = { name: "Alice", age: 30 };
Member Access with ->
// Obiwan uses -> for method / property access

var items = [];
items->push(1, 2, 3);

println(items->length());   // 3
println(items->peek());    // last element

// Math module access
println(Math->PI);           // 3.14159...
println(Math->sqrt(16));    // 4
println(Math->pow(2, 8));   // 256

// Array index access
var arr = [1, 200, 3];
println(arr[1]);            // 200

📦 Variables

Declaration Keywords
// var — function/global scoped
var x = 10;
var name = "Obiwan";
var active = true;
var nothing = null;

// local — block scoped
{
    local y = 20;
    println(y);   // 20
}
// y is not accessible here

// const — cannot be reassigned
const PI = 3.14159;
const greet = fn(n) { println(n); };

// Multiple var on one line
var a = 2, b = 3;
Assignment Operators
var n = 10;

n += 5;    // n = 15
n -= 3;    // n = 12
n *= 2;    // n = 24
n /= 4;    // n = 6
n %= 4;    // n = 2

// Bitwise assignment
n &= 3;    // bitwise AND assign
n |= 1;    // bitwise OR assign
n ^= 2;    // bitwise XOR assign
n <<= 1;   // left shift assign
n >>= 1;   // right shift assign

// Post / pre increment on variables and properties
var z = ["One", 2];
var app = z[1]++ * 3 + 1 + z[1]++ - 2 + ++z[1] + (z[1] *= 2);

🔷 Types & Literals

Primitive Literals
// Number — double precision float
var n1 = 42;
var n2 = 3.14;
var n3 = -0.5;

// String — double quoted
var s = "Hello, World!";
var concat = "foo" + "bar";   // "foobar"

// Boolean
var yes = true;
var no  = false;

// Null
var nothing = null;

println(true, false, null);  // true false null
Type Overview
Type Example Notes
Number 42, 3.14 double precision
String "hello" double-quoted only
Boolean true, false
Null null absence of value
Object { key: val } also arrays
Function fn(x){} first-class

Operators

Arithmetic & Comparison
// Arithmetic
5 + 3    // 8
5 - 3    // 2
5 * 3    // 15
5 / 2    // 2.5
5 % 3    // 2  (modulo)
Math->pow(2, 10)  // 1024 (no ** operator)

// Comparison
3 > 2    // true
3 < 2    // false
3 >= 3   // true
3 <= 2   // false
3 == 3   // true
3 != 2   // true
println(3 > 2);    // true
Logical & Bitwise & Unary
// Logical
true && false    // false
true || false    // true
println(5 && 2);  // truthy short-circuit

// Bitwise
5 & 3     // 1
5 | 3     // 7
5 ^ 3     // 6
2 << 3    // 16
16 >> 2   // 4

// Unary
println(~1);     // bitwise NOT → -2
println(!![]);   // double negation → true
println(+3);     // unary plus → 3
println(-4);     // unary minus → -4

🔧 Functions

Declaration & First-Class
// Named function
fn add(a, b) {
    return a + b;
}
println(add(3, 4));   // 7

// Function expression stored in const
const multiply = fn(a, b) {
    return a * b;
};
println(multiply(3, 5));  // 15

// Immediately invoked function
(fn(param) {
    println(param);
})("Hello World!");

// Recursive function
fn fact(n) {
    if (n == 0) return 1;
    return n * fact(n - 1);
}
println(fact(5));  // 120
Async Functions
// Declare with async keyword after params
fn add(a, b) async {
    return a + b;
}

// Await an async call
fn main() async {
    var result = await add(10, 20);
    println(result);  // 30
}

// Async functions that may throw
fn willThrow() async {
    2 + "focc!";    // runtime error!
    println("HEHE!");
}

// Call without await — fire and forget
main();
println("Done!");   // may print before main resolves
Functions as Arguments
// Functions stored in arrays
var callbacks = [
    fn(index) {
        println("from callback", index);
    },
    fn(index) {
        println("from callback", index);
    }
];

var i = 0;
while (i < callbacks->length()) {
    callbacks[i](i);
    i = i + 1;
}

// Inline function argument (->each iterates values)
items->each(fn(val, idx) {
    println("EACH", val);
});

🔀 Conditionals

if / else
// Standard if / else
if (condition) {
    println("true branch");
} else {
    println("false branch");
}

// Truthy: any non-zero number, non-empty string, object
if (0) {
    println("from then");
} else {
    println("from else");   // prints this
}

if (2) {
    println("foo");   // non-zero → truthy
}

// Nested if
if (x > 0) {
    if (x > 100) {
        println("large");
    } else {
        println("positive");
    }
}
Truthiness Rules
Obiwan evaluates conditions with JavaScript-like truthiness. Any non-zero number, non-empty string, or object is truthy. Zero, empty string, null, and false are falsy.
// Falsy values
if (0)           { } // falsy
if (false)       { } // falsy
if (null)        { } // falsy
if ("")          { } // falsy (empty string)

// Truthy values
if (1)           { } // truthy
if (2)           { } // truthy
if ("hello")     { } // truthy
if ([])          { } // truthy (object)
if ({})          { } // truthy (object)
if (true)        { } // truthy
Optional Parentheses
Parentheses around the condition are optional when the body is a single statement (no braces). This applies to if and, when combined with the do keyword, to while as well.
// No parens — single-statement body
if n == 0 return 1;
if i == 2 continue;
if i == 20 break;

// Works with braces too
if x > 0 {
    println("positive");
}

// while without parens requires the do keyword
var i = 0;
while i < 10 do {
    i++;
}

// Parens always remain valid
if (n == 0) return 1;
while (i < 10) { i++; }
if as an Expression
Like switch, an if / else if / else chain can be used as an expression that evaluates to a value. This form requires every branch to be a single expression (no braces), and a trailing else is required since the expression must always produce a value. The whole chain is typically terminated with a semicolon after the final branch.
// if-expression assigned directly to a variable
var conditionalValue =
    if (true)
        5
    else if (true || num == 2)
        2
    else
        0
    ;

println(conditionalValue);   // 5

// Equivalent to a chain of branches, but as a single expression —
// compare with the statement form, which has no return value:
if (true) {
    conditionalValue = 5;
} else {
    conditionalValue = 0;
}

🔁 Switch

Switch Statement
// Multiple cases can share a body (fall-through grouping)
fn catcher() {
    switch (1003) {
        case 100:
        case 200: {
            println("Hola!");
        }
        default: println(">>> Other!");
    }
}
catcher();  // ">>> Other!" (1003 matches default)

// Switch on strings
fn getLabel(code) {
    switch (code) {
        case "ok": {
            return "Success";
        }
        case "err": {
            return "Error";
        }
        default: return "Unknown";
    }
}
Switch Expression
// Switch used as an expression (returns a value)
println(switch (100) {
    0  => "zero",
    1  => "one",
    2  => "two",
    3  => "three",
    _  => "other"   // wildcard / default
});  // "other"

// Assign the result
var day = 3;
var name = switch (day) {
    1 => "Mon",
    2 => "Tue",
    3 => "Wed",
    4 => "Thu",
    5 => "Fri",
    _ => "Weekend"
};
println(name);  // "Wed"

🔄 Loops

while loop
// Standard while — parens required without do
var g = 0;
while (g < 10) {
    println(g);
    g = g + 1;
}  // 0 1 2 ... 9

// Optional do keyword (parens still valid)
while (g < 20) do {
    g = g + 1;
}

// No parens — do keyword is required
while g < 30 do {
    g++;
}

// Infinite loop pattern
while (1) {
    renderFrame(A, B);
    A = A + 0.07;
    B = B + 0.03;
}
do…while loop
The body runs at least once before the condition is checked. Note: no parentheses around the condition, and no semicolon after it.
var n = 0;
do {
    println("n ->", n);
    n++;
} while n < 10
// prints 0 through 9

// Runs once even when condition is immediately false
var m = 100;
do {
    println("runs once");
} while m < 10

// break / continue work as expected
var k = 0;
do {
    k++;
    if k == 5 break;
} while k < 100
from … to … foreach (range loop)
Iterates a numeric range from start (inclusive) to end (exclusive) with an optional step value. The do keyword opens the body.
// from <start> to <end> foreach <var>, <step> do
from 0 to 5 foreach i, 1 do {
    println(">>>>", i);
}  // 0 1 2 3 4

// Step can be omitted (defaults to 1)
from 0 to 5 foreach i do {
    println(i);
}

// break and continue work inside range loops
from 0 to 40 foreach i, 1 do {
    if i ==  2 continue;   // skip 2
    if i == 20 break;      // stop at 20
    println(">>>>", i);
}
foreach (arrays & objects)
foreach iterates array values or object key-value pairs directly. For arrays, supply one variable (value). For objects, supply two (key, value).
// Array — single variable receives each value
foreach [1, 2, 3] i do {
    if i == 2 continue;
    println(">>>>", i);
}  // >>>> 1  /  >>>> 3

// Works with any array expression or variable
var nums = [10, 20, 30];
foreach nums val do {
    println(val);
}

// Object — two variables receive key and value
foreach {a: "A", b: "B"} k, v do {
    println(">>>>", k, v);
}
// >>>> a A
// >>>> b B
break & continue
// break exits the nearest loop
local x = 2;
while (x < 10) {
    println(x);
    if (x == 5) break;
    x = x + 1;
}  // prints 2 3 4 5

// continue skips the rest of the body
var ll = 0;
while (ll < 10) do {
    ll = ll + 1;
    if ll == 5 continue;
    println("ll ->", ll);
}  // skips 5

// break / continue work through nested try/catch
while (x < 10) {
    try {
        if (x == 5) break;
        x = x + 1;
    } catch (err) { }
}

// Both keywords work in all loop forms:
// while, do...while, from...to...foreach, foreach

🛡️ Try / Catch

Basic Error Handling
try {
    // code that may throw
    2 + "bad type";
} catch (err) {
    print "Error:", err;
}

// return inside try still runs post-try code
fn test() {
    try {
        return "YEH!";
        println("TRY");   // unreachable
    } catch (err) {
        print "CATCH";
    }
    println("FINALLY");
}
println(test());   // "YEH!"
Nested try / catch
try {
    local x = 2;
    while (x < 10) {
        try {
            try {
                println(x);
                if (x == 5)
                    break;
                x = x + 1;
            } catch (err) {
                print "INNER CATCH";
            }
        } catch (err) {
            print "MIDDLE CATCH";
        }
    }
} catch (err) {
    print "OUTER CATCH";
}

// break correctly exits loop even through nested try blocks
Catch with Destructuring
// Missing properties throw at runtime — catch them
try {
    local { a: b, c: d, e: f } = {
        a: "Dog",
        c: "Cat"
        // e is missing — throws
    };
    print "b", b, "d", d;
} catch (err) {
    print "CATCH EEEEE";   // prints this
}

Async / Await

Error propagation across async calls
fn willThrow() async {
    2 + "focc!";       // throws a runtime error
    println("HEHE!");  // never reached
}

fn wrapper() async {
    await willThrow();
    println("continue wrapper!");  // skipped
}

fn caller() async {
    try {
        await wrapper();
        println("continue try!");  // skipped
    } catch (err) {
        print "FROM CATCH!!", err;  // caught here
    }
    println("Continue!!!");  // runs after catch
}

caller();
println("Done!");   // may run before caller resolves
Awaited return value
fn add(a, b) async {
    return a + b;
}

fn run() async {
    var sum = await add(10, 20);
    println(sum);   // 30

    // Chain multiple awaits
    var a = await add(1, 2);
    var b = await add(a, 10);
    println(b);     // 13
}

run();

📋 Arrays

Array Literals & Methods
// Create an array
var nums = [1, 2, 3, 4, 5];
println(nums);   // [1, 2, 3, 4, 5]

// Index access
println(nums[0]);   // 1
println(nums[2]);   // 3

// Mutable array
var items = [];
items->push(1, 2, 3);
println(items->length());  // 3
println(items->peek());   // 3 (last element)

// Iterate with ->each (fn receives value, index)
items->each(fn(val, idx) {
    println("EACH", val);
});
Arrays of Functions
// Store functions in arrays and call them by index
var ops = [
    fn(a, b) { return a + b; },
    fn(a, b) { return a * b; },
    fn(a, b) { return a - b; }
];

println(ops[0](3, 4));  // 7
println(ops[1](3, 4));  // 12
println(ops[2](3, 4));  // -1

// Iterate and invoke
var i = 0;
while (i < ops->length()) {
    println(ops[i](10, 2));
    i = i + 1;
}

🗂️ Objects

Object Literals
// Create an object
var person = {
    name: "Alice",
    age:  30
};

// Access with ->
println(person->name);   // "Alice"

// Objects with method functions
var calculator = {
    Hello: "World",
    World: "Hello",
    add: fn(a, b) {
        return a + b;
    }
};
println(calculator);          // {Hello: "World", ...}
println(calculator->add(3, 4));  // 7

// Property mutation
var pt = { prop: 0 };
var bpp = pt->prop++ + ++pt->prop;
pt->prop = 10;
println(pt, bpp);
Dynamic Property Access
// Properties accessed with -> or []
var config = {
    host:  "localhost",
    port:  8080,
    debug: true
};

println(config->host);    // "localhost"
println(config->port);    // 8080

// Nested objects
var app = {
    db: {
        host: "db.example.com",
        port: 5432
    },
    name: "MyApp"
};

println(app->db->host);   // "db.example.com"
println(app->name);       // "MyApp"

🧩 Destructuring

Array Destructuring
// Unpack array into variables
var [a, b] = [1, 2];
println(a, b);    // 1 2

// From a function returning an array
const returnArray = fn() {
    return [1, 2];
};

var [xx, yy] = returnArray();
print "unpack", xx, yy;   // unpack 1 2

xx = 21;
print "unpack", xx, yy;   // unpack 21 2 (yy unaffected)

// Mix with other var declarations
var x = 2, y = 3;
println(x);     // 2
Object Destructuring
// Unpack named properties (with renaming: a -> b)
var { name, age } = { name: "Alice", age: 30 };
println(name, age);  // Alice 30

// Rename on unpack: { a: b } means "put 'a' into 'b'"
local { a: dog, c: cat } = {
    a: "Dog",
    c: "Cat"
};
println(dog, cat);   // Dog Cat

// Const destructuring from import
const { Add: myAdd } = import("./import.obi");
println(myAdd(1, 1000));  // 1001

// Throws if a required key is missing (use try/catch)
try {
    local { e: f } = { a: "Dog" };  // 'e' missing
} catch (err) {
    print "CATCH EEEEE";
}

🔒 Closures

Closure over local state
// counter() returns a closure that remembers 'i'
const counter = fn() {
    local i = 0;
    return fn() {
        i = i + 1;
        return i;
    };
};

const c = counter();
println(c());  // 1
println(c());  // 2
println(c());  // 3

// Independent counters don't share state
const c2 = counter();
println(c2());  // 1 (fresh)
println(c());   // 4 (continues)
Closures as Callbacks
// Closure captures outer variable
fn makeAdder(n) {
    return fn(x) {
        return x + n;
    };
}

const add5  = makeAdder(5);
const add10 = makeAdder(10);

println(add5(3));    // 8
println(add10(7));  // 17

// Inline callback
fn applyTwice(f, x) {
    return f(f(x));
}
println(applyTwice(add5, 0));  // 10

⛓️ Promises & Chaining

then() chaining
// Async functions return promise-like values
fn add(a, b) async {
    return a + b;
}

// Chain with ->then()
add(0, 1)
    ->then(fn(data) {
        return data + 1;    // 2
    })
    ->then(fn(data) {
        return data * 2;    // 4
    })
    ->then(cb);   // named fn reference

fn cb(data) {
    print "CB", data;       // CB 4
    return data + 1;
}
error() chaining
// Handle promise rejection with ->error()
fn cbError(data) {
    print "CB ERROR", data;
}

// Type error causes rejection
add(0, "foccer")        // will fail
    ->error(fn(data) {
        print "THEN";
    })
    ->error(cbError);

// Mix then and error
add(1, 2)
    ->then(fn(v) { return v * 10; })
    ->error(fn(e) {
        println("caught:", e);
    })
    ->then(fn(v) {
        println("result:", v);
    });

🔢 Math

Constants & Common Functions
println(Math->PI);         // 3.14159265...
println(Math->E);          // 2.71828182...
println(Math->Tau);        // 6.28318530... (2π)

// Power, root
println(Math->pow(2, 8));   // 256
println(Math->sqrt(144));  // 12
println(Math->cbrt(27));   // 3

// Rounding
println(Math->floor(3.7));  // 3
println(Math->ceil(3.2));   // 4
println(Math->round(3.5));  // 4
println(Math->trunc(3.9));  // 3
println(Math->roundTo(3.14159, 2));  // 3.14
Trigonometry & Misc
// Trig (radians)
println(Math->sin(Math->PI / 2));  // 1
println(Math->cos(0));             // 1
println(Math->tan(Math->PI / 4));  // 1
println(Math->atan2(1, 1));        // π/4

// Log & exp
println(Math->log(Math->E));       // 1 (natural log)
println(Math->log10(1000));       // 3
println(Math->log2(8));           // 3
println(Math->exp(1));            // e

// Abs, min, max, clamp, sign
println(Math->abs(-5));           // 5
println(Math->min(3, 7));         // 3
println(Math->max(3, 7));         // 7
println(Math->clamp(15, 0, 10));  // 10
println(Math->sign(-7));          // -1

📁 Path

Path Utilities
// Constants
println(Path->DirectorySeparator);   // / or \
println(Path->PathSeparator);        // : or ;

// File name operations
println(Path->getFileName("/a/b/foo.obi"));
    // "foo.obi"
println(Path->getFileNameWithoutExtension("/a/b/foo.obi"));
    // "foo"
println(Path->getExtension("/a/b/foo.obi"));
    // ".obi"
println(Path->getDirectoryName("/a/b/foo.obi"));
    // "/a/b"

// Combination & checks
println(Path->combine("/usr", "local"));
    // "/usr/local"
println(Path->hasExtension("file.obi"));
    // true
println(Path->changeExtension("a.txt", ".md"));
    // "a.md"
Temp & Qualified Paths
// Temp files
println(Path->getTempPath());
    // e.g. "/tmp/"
var tmp = Path->getTempFileName();
    // creates + returns a temp file path
var rand = Path->getRandomFileName();
    // e.g. "kzpmu4bk.kn0"

// Absolute / relative
println(Path->getFullPath("./script.obi"));
    // absolute path
println(Path->isPathRooted("/etc/hosts"));
    // true
println(Path->isPathFullyQualified("./rel"));
    // false
println(Path->getRelativePath("/a/b", "/a/c/d"));
    // "../c/d"

// Combine multiple path segments
println(Path->combineAll("/usr", "local", "bin"));
    // "/usr/local/bin"

💾 File

Read & Write
// Check existence
if (File->exists("data.txt")) {
    var content = File->readText("data.txt");
    println(content);
}

// Overwrite
File->writeText("out.txt", "Hello!\n");

// Append
File->appendText("log.txt", "New entry\n");

// Read and process
var src = File->readText("src.obi");
println(src->length());   // character count
Copy, Move, Delete
// Delete
File->delete("temp.txt");

// Copy (fails if dest exists)
File->copy("src.txt", "dst.txt");

// Copy with overwrite flag
File->copyWithOverwrite("src.txt", "dst.txt", true);

// Move / rename
File->move("old.txt", "new.txt");

// Safe file write pattern
try {
    File->writeText("output.txt", "data");
    println("Saved.");
} catch (err) {
    println("Write failed:", err);
}

🌍 Global Functions

I/O & OS
// Print
print("no newline");
println("with newline");
print "multi", "args", 42;   // space-separated

// Read from stdin
var input = scan("Enter value: ");

// Console control
clear();   // clears screen
home();    // moves cursor to top-left

// OS detection
println(isWindows());   // true / false
println(isMac());       // true / false
println(isLinux());     // true / false
println(getOsType());   // "windows" | "macos" | "linux" | "unknown"
print vs println syntax forms
// Function call form
print("hello");
println("hello");

// Statement form (no parentheses)
print "hello", x, y;
println "hello";

// Multiple arguments print space-separated
print "CB", data;          // "CB 42"
print "FROM CATCH!!", err; // error message
println(Math, Math->PI, [1,200,3][1]);
// [Math object] 3.14159... 200

📦 Modules

import()
// import() executes a module and returns its exports
const myMod = import("./mymodule.obi");

// Destructure named exports
const { Add: myAdd } = import("./import.obi");
println(myAdd(1, 1000));   // 1001

// Use Math module alongside imports
println(myAdd(1, 1000), Math, Math->PI);
Authoring a module (import.obi)
// import.obi — export functions via provide()
fn Add(a, b) {
    return a + b;
}

fn Subtract(a, b) {
    return a - b;
}

// Return object as the module export value
{
    Add:      Add,
    Subtract: Subtract
}

// In the importer:
// const { Add, Subtract } = import("./import.obi");

💉 Provide / Inject

Dependency Injection Pattern
// provide() registers a value in the frame namespace
fn parent() {
    provide("name", {
        add: fn(a, b) {
            return a + b;
        }
    });
    child();
}

// inject() retrieves the value by key (null if missing)
fn child() {
    println(">>>>>", inject("name")->add(1, 5));
    // >>>>> 6
}

parent();

// inject returns null if key not provided
var missing = inject("nope");
println(missing);  // null
Use Cases for Provide/Inject
Provide/inject is Obiwan's lightweight dependency-injection mechanism. A parent frame registers a service (logger, config, database handle) by name; any descendant function can retrieve it without explicit parameter threading.
// Provide a logger
fn appRoot() {
    provide("log", fn(msg) {
        println("[LOG]", msg);
    });
    runFeature();
}

fn runFeature() {
    const log = inject("log");
    log("Feature started");   // [LOG] Feature started
}

appRoot();

🏛️ Classes

Note: Classes are an in-progress feature. A class body may contain var field declarations and fn method definitions. Inherit from a parent by listing its name in parentheses after the class name.
Class Declaration
// class <Name> { var field; fn method() {} }
class Animal {
    var name = "Animal";

    fn speak() {
        println(name, "makes a sound.");
    }
}

// Multiple fields and methods
class Point {
    var x = 0;
    var y = 0;

    fn move(dx, dy) {
        x = x + dx;
        y = y + dy;
    }

    fn toString() {
        return "(" + x + ", " + y + ")";
    }
}

// A class body may also be empty
class Empty {

}
Inheritance
Place the parent class name in parentheses after the child class name to declare inheritance. The parent's fields and methods are available in the subclass.
// class <Child> (<Parent>) { ... }
class Dog (Animal) {
    var breed = "Mixed";

    fn bark() {
        println("Woof!");
    }

    fn describe(label) {
        println(label, breed);
    }
}

class Cat (Animal) {
    var indoor = true;

    fn meow() {
        println("Meow!");
    }
}
Instantiation with new & this
Use new ClassName(...) to create an instance. Inside a method, the this keyword refers to the current instance, so a field declared with var x = 2; in the class body is reachable as this from any method. The result of new can be used directly, including chaining a -> call onto it without storing it in a variable first.
class Dog (Animal) {
    var x = 2;

    fn method() {
        println(this);   // the Dog instance
    }
}

// Instantiate with new, then call a method via ->
var d = new Dog();
d->method();

// new can also be used inline, chained directly with ->
println("Dog::x", (new Dog())->method());
Class Syntax Reference
Construct Syntax
Declare class class Name { }
Inherit parent class Child (Parent) { }
Field var field = value;
Method fn method(params) { }
Async method fn method(params) async { }
Instantiate new ClassName(args)
Current instance this (used inside methods)

📝 Full Examples

Factorial & Closures

Factorial (recursive)
fn fact(n) {
    if (n == 0) return 1;
    return n * fact(n - 1);
}

println(fact(0));   // 1
println(fact(5));   // 120
println(fact(10));  // 3628800
Counter closure
const counter = fn() {
    local i = 0;
    return fn() {
        i = i + 1;
        return i;
    };
};

const c = counter();
println(c());  // 1
println(c());  // 2
println(c());  // 3

Blocks & Scope

Blocks as expressions & anonymous scopes
// A bare block executes and evaluates its last expression
{
    2 + 2;
    println("HEHEHE!!!!");   // "HEHEHE!!!!"
}

// Blocks create their own scope for local variables
{
    local secret = "only here";
    println(secret);
}
// secret is not accessible outside

// Block scope in switch cases
switch (x) {
    case 1: {
        local temp = "scoped to this case";
        println(temp);
    }
    default: println("other");
}

Loop Forms — Side by Side

All loop types printing 0–4
// while
var i = 0;
while i < 5 do { println(i); i++; }

// do...while
var j = 0;
do { println(j); j++; } while j < 5

// from...to...foreach (range)
from 0 to 5 foreach k, 1 do { println(k); }

// foreach (array values)
foreach [0, 1, 2, 3, 4] v do { println(v); }
Mutating closures inside loops
var i1 = 0;
var mutators = [];

while (i1 < 3) do {
    local obj = { val: i1 };
    mutators->push(fn(v) {
        obj->val = v + obj->val;
        println("MUTATOR", obj);
        obj = { val: i1 };
    });
    i1 = i1 + 1;
}

// Each closure captures its own 'obj'
var ii = 0;
while ii < mutators->length() do {
    mutators[ii](ii * 5);
    ii++;
}

Donut Animation (doughnut.obi)

Rotating ASCII Torus — full example
var A = 0.0;
var B = 0.0;
var screenWidth  = 80;
var screenHeight = 40;
var R1 = 1.0;
var R2 = 2.0;
var K2 = 5.0;
var K1 = screenWidth * K2 * 3.0 / (8.0 * (R1 + R2)) * 0.95;

fn renderFrame(A, B) {
    home();

    local output = [];
    local zbuf   = [];

    local idx = 0;
    while (idx < screenWidth * screenHeight) {
        output->push(" ");
        zbuf->push(0.0);
        idx = idx + 1;
    }

    local theta = 0.0;
    while (theta < 6.28318) {
        local cosTheta = Math->cos(theta);
        local sinTheta = Math->sin(theta);
        local phi = 0.0;
        while (phi < 6.28318) {
            local cosPhi = Math->cos(phi);
            local sinPhi = Math->sin(phi);
            local cosA = Math->cos(A);
            local sinA = Math->sin(A);
            local cosB = Math->cos(B);
            local sinB = Math->sin(B);
            local circleX = R2 + R1 * cosTheta;
            local circleY = R1 * sinTheta;
            local x = circleX * (cosB * cosPhi + sinA * sinB * sinPhi)
                     - circleY * cosA * sinB;
            local y = circleX * (sinB * cosPhi - sinA * cosB * sinPhi)
                     + circleY * cosA * cosB;
            local z   = K2 + cosA * circleX * sinPhi + circleY * sinA;
            local ooz = 1.0 / z;
            local xp  = Math->floor(screenWidth  / 2.0 + K1 * ooz * x);
            local yp  = Math->floor(screenHeight / 2.0 - K1 * ooz * y * 0.5);
            local luminance =
                cosPhi * cosTheta * sinB
                - cosA * cosTheta * sinPhi
                - sinA * sinTheta
                + cosB * (cosA * sinTheta - cosTheta * sinA * sinPhi);
            local L = Math->floor(luminance * 8.0);
            if (L > 0) {
                if (xp >= 0 && xp < screenWidth && yp >= 0 && yp < screenHeight) {
                    local pos = Math->floor(yp * screenWidth + xp);
                    if (ooz > zbuf[pos]) {
                        zbuf[pos] = ooz;
                        local chars   = ".,-~:;=!*#$@";
                        local charIdx = L;
                        if (charIdx > 11) charIdx = 11;
                        output[pos] = chars[charIdx];
                    }
                }
            }
            phi = phi + 0.07;
        }
        theta = theta + 0.07;
    }

    local row = 0;
    local frame = "";
    while (row < screenHeight) {
        local line = "";
        local col = 0;
        while (col < screenWidth) {
            line = line + output[row * screenWidth + col];
            col = col + 1;
        }
        frame = frame + line + "\n";
        row = row + 1;
    }

    print(frame);
}

while (1) {   // infinite animation loop
    renderFrame(A, B);
    A = A + 0.07;
    B = B + 0.03;
}

⚙️ C# Host API

Note: This section covers the .NET host used to run Obiwan scripts. Obiwan scripts are compiled and executed via the Compiler and Vm types in the obiwan namespace.
CLI Entry Point (Program.cs)
namespace obiwan;

public static class Program
{
    private static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            PrintHelp();
            return;
        }

        switch (args[0])
        {
            case "-h":
            case "--help":
                PrintHelp();   break;

            case "-t":
            case "--test":
                RunTestSuite();   break;

            case "-r":
            case "--run":
                if (args.Length < 2) {
                    Console.WriteLine("Error: Missing file path.");
                    return;
                }
                ExecuteScript(args[1]);   break;

            default:
                Console.WriteLine($"Unknown option: {args[0]}");
                PrintHelp();   break;
        }
    }
ExecuteScript & Test Suite
    private static void ExecuteScript(string path)
    {
        if (!File.Exists(path))
        {
            Console.WriteLine(
                $"Error: '{path}' does not exist.");
            return;
        }

        var absPath = Path.GetFullPath(path);
        var source  = File.ReadAllText(absPath);
        var state   = new State();
        state.PushDir(
            Path.GetDirectoryName(absPath) ?? ".");

        var compiler = new Compiler(state, absPath, source);
        var script   = compiler.Compile();

        var vm = new Vm(state);
        vm.MainLoop(script);
        vm.Dispose();
        state.Dispose();
    }

    private static void RunTestSuite()
    {
        Console.WriteLine("Running test harness...");
        // assertions here
        Console.WriteLine("All tests passed.");
    }

    private static void PrintHelp()
    {
        Console.WriteLine("Obiwan CLI");
        Console.WriteLine("  -r, --run <path>   Run a script");
        Console.WriteLine("  -t, --test         Run test suite");
        Console.WriteLine("  -h, --help         This help text");
    }
}
CLI Usage
# Run a script
obiwan --run ./tests/lang.obi

# Run the internal test suite
obiwan --test

# Show help
obiwan --help
obiwan -h

# Short form
obiwan -r ./script.obi
Host Architecture
Type Role
State Global interpreter state; manages directory stack
Compiler Parses and compiles .obi source to bytecode
Vm Bytecode virtual machine; runs MainLoop
AutoLoader Injects Math, Path, File, globals into every frame
ObValue Runtime value type (Number, String, Object, Function…)