Balaur Script reference
Every construct of Balaur Script: types, values, operators, strings, lists, maps, optionals, structs, classes, enums and match, functions, any and modules.
Bu sayfada
What each construct does, and what it refuses. The examples are programs
from the compiler's test corpus (crates/balaur_bs/tests/programs/), which
runs every one on wasmi, wasmtime, native code and the browser's engine and
holds them to the same answer. Scripting covers how a
script meets a node.
A file
A .bs file holds items in any order: fn, struct, class, enum,
impl, const, extern fn and use. pub makes an item reachable from
outside the file. // starts a comment.
const NAME = "ball";
pub fn label(hp: int) -> str { "{NAME}: hp {hp}" }
Types
| Type | Holds |
|---|---|
int |
a 64-bit signed integer |
float |
a 64-bit float |
bool |
true or false |
str |
UTF-8 text, counted by reference |
Node |
a node of the scene, or none |
[T] |
a list, counted by reference |
{str: V}, {int: V} |
a map, kept in the order keys were first set |
T? |
a T, or nil |
any |
a value of any kind, looked at when the script runs |
fn(A, B) -> R |
a function, named elsewhere |
a struct |
a value, copied field by field |
a class |
one object every holder shares |
an enum |
one of its variants, a value |
Vec2, Vec3 and Color are structs the engine declares, and a list of
numbers stands where one goes: let p: Vec3 = [1.0, 2.0, 3.0];.
Variables and constants
let declares a variable, typed by its value or by what it names:
let n = 3;, let x: float = 3;. A variable can be assigned again, and
+=, -=, *=, /= and %= combine. A let inside a block ends with
the block. A const is fixed for the file, and a module's reads as
util::NAME.
Numbers
+,-and*on ints wrap past the ends of 64 bits./on ints truncates toward zero. Dividing by zero traps, and so does the smallest int divided by-1.%takes the sign of the left side.%by zero traps.- An int meets a float as a float:
n / 2.0is a float, andlet x: float = 3holds3.0. asconverts the rest:int as float,bool as int, andfloat as int, which truncates; a float past an int's range gives the nearest end, and NaN gives 0.- Floats follow IEEE 754, the same bits on every engine. A NaN the host sees is always the same NaN.
Strings
const NAME = "ball";
pub fn label(hp: int, speed: float, alive: bool) -> str {
"{NAME}: hp {hp}, speed {speed}, alive {alive}, {{braces}}"
}
// label(7, 2.0, true) is "ball: hp 7, speed 2.0, alive true, {braces}"
"{x}"writes a value into the text, ananyas itstext();{{and}}write braces.\n,\t,\",\\and\u{e9}are escapes.+joins two strings and==compares them.lencounts bytes.slice(a, b)takes byte offsets; an offset inside a character moves back onto it, and one past the end stops at the end.contains,starts_with,ends_with,find(or-1),replace,split,split_once,chars,trim,to_upper,to_lower,parse_intandparse_float(0when the text is no number).
Lists
pub fn shuffle() -> [int] {
let xs = [1, 2, 3];
xs.insert(0, 0);
xs.insert(4, 4);
xs.insert(2, 9);
let gone = xs.remove(2);
xs.push(gone);
xs
}
// shuffle() is [0, 1, 2, 3, 4, 9]
[a, b], and[value; count]forcountcopies.push,pop,insert,remove,extend,sort,len, andatorxs[i]to read. An index past either end traps.- A list is shared, not copied:
let b = a; b.push(1);changesatoo. for x in xswalks it in order.
Maps
pub fn ages() -> {str: int} {
let ages: {str: int} = {"ada": 36, "alan": 41};
ages["grace"] = 85;
ages["ada"] += 1;
ages
}
- A key is a
stror anint. A value is never nil: a missing key is. m[k]reads aV?;m[k] = vsets.m[k] += 1on a missing key traps.has,remove,len,keysandvalues, in the order keys were set; a key removed and set again goes to the end.- A
{str: V}goes into ananyas a map and comes back out of one. - A literal is typed by where it goes:
let m = {"a": 1};does not compile.
Optionals
A T? is a T or nil. It is used through its methods: is_some,
is_none, unwrap, which traps on nil, and or(fallback). x == nil
compares. A value that is always there has none of them, and an optional
cannot itself be optional.
pub fn names(k: int) -> str {
let s: str? = if k > 0 { "pos {k}" } else { nil };
s.or("none")
}
Structs and classes
A struct is a value: assigning or passing one copies its fields. A
class is one object on the heap, shared by everyone holding it. A class
holds itself through C? or a list; a struct only through a list.
pub struct Label { kind: str = "label", size: int = 3, text: str, tip: str? }
pub fn plain() -> str {
let label = Label { text: "hi" };
"{label.kind} {label.size} {label.text} {label.tip.is_none()}"
}
// plain() is "label 3 hi true"
A literal names what differs from the declared defaults. A field left out
takes its default, or nil when it is optional; any other field must be
named. impl Type { .. } gives a type its methods, with self writable.
Enums and match
An enum's variants carry nothing, values by position, or named fields.
match takes the value apart.
enum Shape { Circle(float), Rect { w: float, h: float }, Empty }
fn area(s: Shape) -> float {
match s {
Shape::Circle(r) => r * r * 3.0,
Shape::Rect { w, h } => w * h,
Shape::Empty => 0.0,
}
}
- A variant is made by its path:
Shape::Circle(2.0),Shape::Rect { w: 1.0, h: 2.0 },Shape::Empty. - An arm names a variant and binds its values: by position, by field name,
h: tallto rename, and_to bind nothing. _takes any value; a bare name such asothertakes any value and binds it.- A
matchnames every variant once, or ends with an arm that takes any value. A missed variant, one named twice, or an arm after a catch-all does not compile. - The subject is worked out once. An arm may
return,breakorcontinue, and in anasync fnan arm of amatchstatement mayawait. - An enum is a value, like a struct, so one cannot hold itself.
- A module's enum is reached by its path:
shapes::Shape::Circle(1.0).
Functions
fn double(x: int) -> int { x * 2 }
fn apply(f: fn(int) -> int, x: int) -> int { f(x) }
pub fn twice(k: int) -> int { apply(double, k) }
- The last expression of a body, with no
;, is its value;returnleaves early. - A function is a value:
let f = double;, stored in a field, a list or anany. There are no closures, so a function value names a function. fn grow(w: &mut Wave)writes back to the caller's own variable or field when it returns. The argument must be a variable or a field, and one place is never passed to two&mutparameters of one call.
Control flow
ifis a value when both branches give one:let sign = if clockwise { 1.0 } else { -1.0 };.while cond { .. },loop { .. },for i in a..b { .. }andfor x in xs { .. }, withbreakandcontinue.&&and||stop at the first side that decides.
any
An any holds what has no one type: an HTTP reply, a save, a host's
payload. Ints, floats, bools, strings, nodes, lists, structs and optionals
box into one where one is wanted, and an any becomes the typed value a
place asks for: let n: int = a["n"];.
kind, is_nil, as_int, as_float, as_bool, as_str, as_node,
len, get, at, has, keys, text, with, set, push, put,
insert, remove, equals and sorted. a.name reads a.get("name").
map().with("k", v) builds a map. A map or a list in an any changes in
place for every holder.
Modules
use util; links util.bs from beside the file or a folder above it, and
its pub items read as util::name. use a::b; is a/b.bs. Files may use
each other, and the file that uses them.
Async
async fn may await a host call or another script async fn, and the
host resumes it on a later frame. An async fn takes no &mut parameter:
its caller runs on while it waits.
Errors
A compile error names its line and column: 6:7: cannot combine int and bool. A file that reads gives one error for each function that does not
compile. A trap
at run time (an index out of range, unwrap on nil, a division by zero, a
missing map key under +=) ends the call, and the next call runs.
Not in the language
Generics, traits, closures, T?? and any?.