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…) |