-
Notifications
You must be signed in to change notification settings - Fork 0
Language Reference
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.
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 — 千里之行,始于足下.
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.
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
// 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.
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.
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.
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 readscountresolve 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.
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.
// 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.
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.
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.
// 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).
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).
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.
- Arena: block-based pool; allocations never move, never freed mid-run.
-
Limit:
bio -e 256M file.bio(default 256 MiB interpreted, unlimited compiled unlessBIO_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.
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.
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.
- Home
- Beginner — first steps
- Intermediate — real usage
- Advanced — masterclass
- Build & Run
- Packaging
- Language-Reference
- BR-Model
- BTM-Model