Skip to content

Language Reference

Enoch-199811 edited this page Aug 9, 2026 · 1 revision

Language Reference — every syntax, in order

English | 中文 | Русский | Español | Português | 繁體中文 | Deutsch

The complete grammar, taught in a deliberate order: skeleton → data → control → requests → functions → streams → classes → assumptions → annotations → references → IO → concurrency → native → memory → shipping. Every section carries a real example, and many carry a philosophical proposition, a proverb, or a story. Run everything with bio file.bio.

1. The skeleton

program main;               // this file is a main program

Main {                      // the main program stream
    void exec() {           // the entry method
        CIO::println("Hello, world!");
    }
}
  • program main; — declares an entry point.
  • program utils; — declares a toolbox (library, no entry).
  • Main — the main stream; void exec() runs at startup.
  • // line comment, /* block comment */.

Proverb. Every journey begins with a single request — 千里之行,始于足下.

2. Types and declarations

Base types:

int age = 30;
float pi32 = 3.14;
double pi = 3.14159265358979;
string name = "BioLang";
char grade = 'A';

Qualifiers:

const int SPEED = 9;        // read-only program constant
thread int note = 0;        // per-thread variable

Arrays:

int[] a = new int[4];       // fixed-size array of 4 zeroes
a[0] = 10;                  // indexed read/write
int x = a[1];
ALL v = new Array(3);       // growable Array class
v::push(40); v::push(50);

Philosophy. Types are contracts with the future — they bind what a value may become. Heraclitus: "No man ever steps in the same river twice"; a typed variable, however, is the same river by name, which is why BioLang adds smart references (see §11) to control who may touch the water.

3. Operators

int a = 7 + 3;              // 10    arithmetic: + - * / %
int b = a * 2;              // 20
int c = b % 7;              // 6     remainder
a++;                        // increment / decrement: ++ --
b--;

int p = 2 * 3 + 4;          // precedence: * / % before + -
int q = (2 + 3) * 4;        // parentheses win

4. Control flow

// if / else if / else
if (score >= 90) { CIO::println("A"); }
else if (score >= 60) { CIO::println("B"); }
else { CIO::println("C"); }

// while
int i = 0;
while (i < 3) { CIO::println(i); i++; }

// for — note the trailing ';' after the update clause
for (int j = 0; j < 5; j = j + 1;) {
    if (j == 2) continue;   // skip 2
    if (j == 4) break;      // stop at 4
    CIO::println(j);
}

Story — Zeno's paradox. Achilles and the tortoise race. Each while iteration halves the gap but never closes it — unless, like BioLang, you count the steps:

double gap = 100.0;
int steps = 0;
while (gap > 0.0001) {
    gap = gap / 2;
    steps++;
}
CIO::println("Achilles catches up after", steps, "steps");

Proverb. 塞翁失马 (The old man lost his horse) — a while loop may lose the horse (miss the condition) and yet gain the herd (finish later); the if inside decides what each iteration means.

5. The request model

ALL r = div(10, 3);         // capture the whole request result
CIO::println(get r);        // 3        (unwrap success)
CIO::println(get div(10, 0));
// refused: division by zero
CIO::println(cause div(10, 0));   // the refusal reason

Every call is a request that succeeds with a value or refuses with a reason — never both, never neither. get unwraps success, cause gets the reason, ALL keeps both (x.res, x.cause).

Philosophy — Schrödinger's cat. Before unwrapping, a request is both Res and Ref — the cat is alive and dead. get opens the box; cause tells you why the cat is dead. BioLang makes the box explicit, so you always know when you are opening it.

Proverb. Every cloud has a silver lining — a Ref carries a cause, and the cause is often the instruction you needed.

6. Functions and methods

void add(a int, b int) { res a + b; }        // respond
void div(a int, b int) { ref "division by zero"; }   // refuse

// multiple return values
void triple(a int) { res a, a * 2, a * 3; }
ALL t = triple(10);        // t.res = [10, 20, 30]

// bare calls inside a stream
Main {
    void exec() {
        CIO::println(get add(2, 3));   // 5
    }
}

Return syntax: res expr; (respond), ref "reason"; (refuse), res a, b, c; (multi-value, arrives as an array). ref "nothing" is an implicit success.

7. Streams: signatures, forks, fields

Stream Greeter {                    // signature: the contract
    void greet(name string);
    int count;
}

Greeter Friendly {                  // fork: the implementation
    void greet(name string) {
        this::count = count + 1;    // this::field — the stream's own slot
        CIO::println("hello,", name);
    }
}
  • Signature declares what; fork defines how — one contract, many implementations (a plug board, not a single wire).
  • Bare calls greet("x") and field reads count resolve through the scope chain; this:: addresses the stream itself.
  • A call by signature name falls back to its implementation stream.

Metaphor. Streams are rivers: the signature is the river's name, each fork a branch (三角洲). Water (data) flows through whatever branch you connect — and a branch may carry different water to different towns.

8. Classes and objects

Class Hero {
    int hp;
    void __init__() { this::hp = 100; }   // constructor
    void takeDamage(d int) { this::hp = hp - d; }
}

Main {
    void exec() {
        Hero h = new Hero();              // instantiate
        h::takeDamage(30);
        CIO::println("hp =", h.hp);       // 70
        h::set("armor", 50);              // dynamic attribute
        CIO::println(get h::get("armor"));
    }
}

A class is a stream you can instantiate; an object is a stream holding its own attributes. Obj::set/get manage attributes dynamically.

Philosophy — the Ship of Theseus. If you replace every plank, is it the same ship? BioLang's answer: new builds the ship, __init__ lays the keel, attributes are the planks. The object's identity is the stream; its state is replaceable. This is why attributes can be washed away — and why a washed-away attribute refuses access: the plank is gone, the ship remains.

9. The need assumption system

// main.bio
program main;
need value GREETING;        // assume a constant
need function greet;        // assume a bare function
need Class Hero;            // assume a class
need Stream Greeter;        // assume a stream

// utils/config.bio  (provider)
program utils;
const string GREETING = "Hello from utils/";

bio build collects providers recursively from src/ + utils/ + dependencies until the closure stabilizes. A need without a provider is an error — the build refuses, telling you exactly which plate is missing.

Metaphor. A jigsaw puzzle: each need is a missing piece, each provider is the piece that fits; bio build assembles the table until no hole remains — and if a piece is missing, it shows you the hole.

10. Annotations

Stream Sealed { void ping(); } @unfork        // cannot be forked again
Class Frozen { int n; } @unfork               // cannot be `new`-ed

Stream ReadOnly { int n; void bump(); int get(); }
ReadOnly RO {
    void bump() { this::n = n + 1; } @write   // explicitly a writer
    int get() { res n; } @read                // explicitly a reader
} @onlyread                                   // users may not call writers

void fastSum(n int) { ... } @call             // phone booth per thread
void shared() { ... } @ucall                  // one global phone booth
Annotation Target Meaning
@read / @write method declare read/write nature (beats heuristics)
@onlyread stream refuse write-method calls
@unfork stream/class refuse all fork paths, incl. new
@call method phone booth, one per thread
@ucall method phone booth, single global

Story — the phone booth. In a village with one phone booth (a fixed wooden box), a caller steps in, the door locks (in_use), the booth is swept clean before the call, and the caller leaves it ready for the next person. No recursion: you cannot be inside the booth while you are already inside it — the door refuses (refused: phone-booth method ... does not support recursion). Every street corner has its own booth (@call), so two callers in different streets never wait on each other; but the town hall has one booth for everyone (@ucall), and whoever is inside keeps everyone else out — with a refusal, not a queue.

11. Smart references

int counter = 0;
&w u int wr = &counter;         // writable, program-level
Ref::write(wr, 10);

&r u int rr = &counter;         // read-only
CIO::println(get Ref::read(rr));      // 10
CIO::println(cause Ref::write(rr, 1));
// Ref refused: reference is read-only, cannot write

&m u int mp = &a[1];            // moving pointer (like C's a++)
mp++;                           // advance the pointer
CIO::println(get mp);

28 reference types = 7 permission stacks (r w m rw rm wm rwm) × 4 follows (u program, f method, a area, t thread).

Metaphor. A reference is a key: the permission is what the key may open (read-only keys open the window, not the door), the follow is which door (the program's front door, the method's room, the thread's locker). A m-permission key is a master key that moves: turn it, and it opens the next door in the corridor.

12. IO: CIO / FIO / SIO / Com / Time / Rem

// Console
CIO::print("name? "); string n = CIO::getln();
CIO::println("hello,", n);
int k = CIO::readInt();

// File
FIO::open("notes.txt", "w"); FIO::println("line"); FIO::close();
FIO::open("notes.txt"); string line = FIO::getln(); FIO::close();
string all = FIO::readFile("notes.txt");        // whole file

// String buffer
SIO::println("Hello"); SIO::print("World");
CIO::println(SIO::content());
CIO::println(SIO::format("%d + %d = %d", 2, 3, 5));   // printf-style

// Computation + timing
CIO::println(get Com::sqrt(9));        // 3
Time::start(); Time::sleep(50); CIO::println(get Time::elapsed());

// Memory stream
Rem::set("key", 42); CIO::println(get Rem::get("key"));

IO is the abstract parent: use CIO (console), FIO (file), or SIO (string).

13. Concurrency

Calc Worker { void factorial(n int) { ... } }

ALL t1 = Threads::spawn("factorial", 10);   // start a bare method
ALL r1 = Threads::join(t1);                 // wait + fetch result
CIO::println("live threads:", get Threads::active());

Taskm::interval(1);                         // rotate every ~1 ms
ALL job = Taskm::add("jobA", 5);
Taskm::run();                               // until all tasks finish

Cooperative: a thread runs until it yields/blocks/finishes — no preemption, no data races, but a thread that never yields starves the kitchen (see BTM Model).

14. Native libraries

Stream m & "libm.so.6";         // bind a shared library

Main {
    void exec() {
        CIO::println(get m::sin(0));         // 0
        CIO::println(get m::pow(2, 10));     // 1024
    }
}

Exported symbols become stream methods (double(*)(double,...), up to 6 args). The platform shim uses dlopen/LoadLibrary transparently.

15. Memory model

  • Arena: block-based pool; allocations never move, never freed mid-run.
  • Limit: bio -e 256M file.bio (default 256 MiB interpreted, unlimited compiled unless BIO_MEM_LIMIT); hitting the limit stops with a clear message.
  • Phone booths: fixed regions, reset per call, per-thread or global — zero allocation, zero fragmentation.

Metaphor. The arena is a library that never throws books away: shelves are added, books never move, and the librarian (arena head) always knows the next free spot. Phone booths are reading rooms: swept before each reader, kept standing between readers.

16. Compiling and shipping

bio file.bio                    # interpret
bio shell build file.bio        # → bin/file (standalone executable)
bio build myapp -s              # project → standalone
bio build myapp -m              # project → .img package (app + CLI + libs)
bio build myapp -m dist.zip     # → .zip package
bio pack hello.img --entry hello bin/hello
bio run hello.img               # run a package
make bin                        # cross-compile 7 platform trees (zig)

See Packaging for formats.

Story. You wrote a recipe (source). shell build bakes it into a single cake (standalone binary). build -m packs the cake with the oven and the kitchen (platform CLI + runtime) into a box (.img/.zip) anyone can carry. make bin bakes seven cakes in seven regional kitchens at once.

Appendix: grammar quick sheet

program       := "program" ( "main" | ident ) ";"
stream        := ("Stream"|"Class") ident ( "&" string )? "{" decl* "}" annot*
fork          := ident ident "{" decl* "}" annot*
method        := rettype ident "(" params ")" block annot?
return        := "res" expr ("," expr)* ";" | "ref" string ";" | "res;"
assume        := "need" ("value"|"function"|"Class"|"Stream") ident ";"
refdecl       := "&" perms follow basetype ident "=" "&" lvalue ";"
annot         := "@" ("read"|"write"|"onlyread"|"unfork"|"call"|"ucall")
control       := if | while | for | break | continue
for           := "for" "(" init ";" cond ";" update ";" ")" block

Run the 15 commented examples for every feature in executable form.

Clone this wiki locally