Introduction to BuLang
BuLang is a production-ready, stack-based bytecode virtual machine designed specifically for game development. It provides high-performance script execution with support for object-oriented programming, first-class functions, typed buffers for binary data, and a unique process system for cooperative multitasking.
All 11 comprehensive test suites passing with 100% coverage. The language is stable, battle-tested, and ready for production use with proven support for 50K+ concurrent processes, typed buffers, exception handling, and more.
Key Features:
- Cooperative Multitasking: Handle 50,000+ concurrent processes @ 30+ FPS with fibers and yield support
- Typed Buffers: Native binary data support (UINT8, INT16, INT32, FLOAT) for image processing, audio, and file I/O
- Modern Control Flow: Foreach loops, try-catch exception handling, and goto/gosub for low-level control
- Full OOP: Classes with inheritance, structs, methods, and properties
- Module System: Zero-overhead native function calls with compile-time tree shaking
- Battle-Tested: Stress-tested with deep recursion (50 levels), large arrays (1000+ elements), and complex inheritance chains
The language combines simplicity with power, allowing you to write game logic, AI behaviors, interactive systems, and process binary data efficiently - all while maintaining clean, readable code.
Getting Started
BuLang is designed to be easy to learn for developers coming from any programming background.
Hello World
Start with the simplest program:
// Hello World
print("Hello, BuLang!")
The print() function outputs text to the console. That's all you need to get started!
Variables and Basic Types
BuLang uses dynamic typing - you don't need to declare variable types:
// Variables
var name = "BuLang";
var version = 1.0;
version = "ola";
var count = 42;
var is_active = true;
print(name);
print(count);
Language Basics
Data Types
BuLang supports the following primitive types:
| Type | Example | Description |
|---|---|---|
| Number | 42, 3.14 | Integer or floating-point numbers |
| String | "hello" | Text values |
| Boolean | true, false | Truth values |
| Null | nil | Absence of value |
| Array | [1, 2, 3] | Collections of values |
| Map | {x: 10, y: 20} | Key-value pairs |
| Buffer | @(1024, TYPE_UINT8) | Typed binary data arrays |
Operators
// Arithmetic
a = 10 + 5 // 15
b = 10 - 5 // 5
c = 10 * 5 // 50
d = 10 / 5 // 2
e = 10 % 3 // 1
// Comparison
a == b // false
a != b // true
a > b // true
a >= b // true
// Logical
true && false // false
true || false // true
! true // false
Bitwise Operations
a & 0b1010 // 0b1000 AND
a | 0b1010 // 0b1110 OR
a ^ 0b1010 // 0b0100 XOR
a << 2 // 40 Left Shift
a >> 2 // 2 Right Shift
(~0) == -1 Bitwise NOT
// Assignment
a = 10
a += 5 // a = a + 5
a -= 5 // a = a - 5
a *= 5 // a = a * 5
a /= 5 // a = a / 5
a %= 5 // a = a % 5
// Increment and Decrement
a++ // a = a + 1
a-- // a = a - 1
Arrays
var arr = [1, 2, 3];
var empty = [];
var mixed = [1, "hello", true, nil,v];
print(mixed);
// Acesso
print(arr[0]); // 1
print(arr[-1]); // 3 (Python-style!)
// Modificar
arr[1] = 10;
print(arr[1]); // 10
// MΓ©todos
arr.push(4);
print(arr.len()); // 4 or lenght()
var x = arr.pop();
print(x); // 4
arr.clear();
print(arr.len()); // 0
// Loop
var nums = [10, 20, 30];
for (var i = 0; i < nums.len(); i++) {
print(nums[i]);
}
// 2D arrays
var matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
print(matrix[1][2]); // 6
Maps
//Create
var player = {
name: "Hero",
hp: 100,
level: 5
};
var empty = {};
// Acess
print(player["name"]); // "Hero"
player["hp"] = 80;
print(player["hp"]); // 80
// Methods
print(player.len()); // 3
if (player.has("level")) {
print("Has level");
}
var keys = player.keys();
print(keys); // ["name", "hp", "level"]
var values = player.values();
print(values); // ["Hero", 80, 5]
player.remove("level");
print(player.len()); // 2
player.clear();
print(player.len()); // 0
// Nested
var game = {
player: {
x: 100,
y: 200
},
enemies: [
{x: 50, y: 50},
{x: 150, y: 150}
]
};
print(game["player"]["x"]); // 100
print(game["enemies"][0]["y"]); // 50
// Loop sobre keys
var config = {width: 800, height: 600, fps: 60};
var keys = config.keys();
print(keys);
for (var i = 0; i < keys.len(); i++)
{
var key = keys[i];
var value = config[key];
write(" key {} : value {}\n", key,value);
}
String Methods
// String operations
var text = "Hello, BuLang!";
// Length & Access
print(text.len()); // 14
print(text[0]); // "H"
print(text[-1]); // "!"
// Methods
text.upper(); // "HELLO, BULANG!"
text.lower(); // "hello, bulang!"
text.contains("BuLang"); // true
text.indexOf("Lang"); // 8
text.split(", "); // ["Hello", "BuLang!"]
text.trim(); // Remove whitespace
// Concatenation
var greeting = "Hello" + " " + "World";
Structs
struct Point {
x,y;
};
var point = Point(1,2);
print(point.x);
print(point.y);
Control Flow
// If statements
x = 10
if x > 5 {
print("x is greater than 5")
} elif x == 5 {
print("x is 5")
} else {
print("x is less than 5")
}
// Loops
for i = 0; i < 5; i = i + 1 {
print(i)
}
// While loop
count = 0
while count < 3
{
print(count)
count = count + 1
}
// do while loop
count = 0
do
{
print(count)
count = count + 1
} while (count < 3)
// Switch statement no fallthrough
switch x {
case 1:
print("x is 1")
case 2:
print("x is 2")
default:
print("x is not 1 or 2")
}
//continue and break
for i = 0; i < 5; i = i + 1 )
{
if (i == 2 )
{
continue;
}
if (i == 3 )
{
break;
}
print(i);
}
Foreach Loops
BuLang provides native foreach loops for iterating over arrays and other iterable collections. This is more concise and cleaner than traditional for loops when you need to process every element.
Basic Syntax
// Iterate over array
var numbers = [10, 20, 30, 40, 50];
foreach (num in numbers)
{
print(num);
}
// Output: 10, 20, 30, 40, 50
With Break and Continue
You can use break and continue inside foreach loops:
// Find first element > 25
var values = [10, 20, 30, 40, 50];
var found = nil;
foreach (v in values)
{
if (v > 25)
{
found = v;
break; // Exit loop early
}
}
print(found); // 30
Foreach with Strings
// Concatenate strings
var words = ["Hello", " ", "BuLang", "!"];
var sentence = "";
foreach (word in words)
{
sentence = sentence + word;
}
print(sentence); // "Hello BuLang!"
The loop variable (num, v, etc.) is scoped to the foreach block and doesn't affect variables with the same name outside the loop.
Try-Catch Exception Handling
BuLang supports structured exception handling with try-catch blocks. This allows you to gracefully handle errors and prevent crashes.
Basic Syntax
// Catch division by zero
def safe_divide(a, b)
{
if (b == 0)
{
throw "Cannot divide by zero!";
}
return a / b;
}
try
{
var result = safe_divide(10, 2);
print(result); // 5
result = safe_divide(10, 0); // This throws
print("This won't execute");
}
catch (error)
{
print("Error: " + error);
}
print("Program continues!");
// Output:
// 5
// Error: Cannot divide by zero!
// Program continues!
Throwing Exceptions
Use the throw statement to raise an exception:
// Custom validation
def validate_age(age)
{
if (age < 0)
{
throw "Age cannot be negative";
}
if (age > 150)
{
throw "Age is unrealistic";
}
return true;
}
try
{
validate_age(-5);
}
catch (err)
{
print("Validation error: " + err);
}
// Output: Validation error: Age cannot be negative
Nested Try-Catch
// Multiple error handling layers
try
{
print("Outer try");
try
{
print("Inner try");
throw "Inner error";
}
catch (inner_err)
{
print("Caught inner: " + inner_err);
throw "Outer error"; // Re-throw or new error
}
}
catch (outer_err)
{
print("Caught outer: " + outer_err);
}
Use try-catch for recoverable errors (invalid input, missing files). Use assertions for programming errors that should never happen in production.
Goto & Gosub
BuLang supports low-level control flow with goto and gosub statements. These are useful for implementing state machines, custom loops, and subroutines within processes.
While powerful, goto and gosub can make code harder to understand. Use them primarily in processes where they provide clear benefits.
Goto - Unconditional Jump
The goto statement jumps to a labeled position in the code. Labels are defined with label_name:
// Simple goto example
process loop_test()
{
var i = 0;
loop_start:
write("loop {} \n", i);
i += 1;
if (i < 5) goto loop_start;
print("Loop finished!");
exit;
}
loop_test();
Gosub - Subroutine Call
The gosub statement jumps to a label and expects a return to come back to the next instruction.
// Gosub example
process teste() {
print("A");
gosub sub;
print("C");
exit;
sub:
print("B");
return;
}
teste();
// Output: A, B, C
Combining Goto and Gosub
You can combine both for complex control flow:
// Loop with subroutine
process loop_with_sub()
{
var i = 0;
mainloop:
gosub body;
i += 1;
if (i < 5) goto mainloop;
exit;
body:
write("iteration {} \n", i);
return;
}
loop_with_sub();
Nested Gosub Calls
You can nest gosub calls - each return goes back to the caller:
// Nested gosub
process nested_example() {
print("start");
gosub a;
print("end");
exit;
a:
print("A");
gosub b;
print("A return");
return;
b:
print("B");
return;
}
nested_example();
// Output: start, A, B, A return, end
Include External Files
You can include external BuLang files using the include directive:
// Include example
include "strings.cc";
// Now you can use functions from strings.cc
str = "hello";
upper_str = to_upper(str);
print(upper_str);
Best Practices
- Use within processes:
gotoandgosubwork best insideprocessblocks - Clear labels: Use descriptive label names like
mainloop:,cleanup:,error_handler: - Forward jumps: Prefer jumping forward to avoid creating unreadable code
- Subroutines: Use
gosubfor repeated code blocks within a process - Always exit: Make sure to use
exitto properly terminate processes
Common Use Cases
| Use Case | Recommended Statement | Example |
|---|---|---|
| Game loops | goto |
Main game loop, frame updates |
| State machines | goto |
AI behavior states, UI states |
| Reusable code blocks | gosub |
Drawing routines, cleanup code |
| Error handling | goto |
Jump to error handler label |
Built-in Math Opcodes
BuLang includes mathematical functions as native VM opcodes, providing zero-overhead execution comparable to compiled languages. These functions execute directly in the VM dispatch loop without any function call overhead.
Math operations are compiled to dedicated opcodes (OP_SIN, OP_COS, etc.), not function calls. This means physics calculations, AI pathfinding, and graphics transforms execute at near-native speed.
Unary Math Functions (1 Argument)
These functions take a single numeric argument:
| Function | Opcode | Description |
|---|---|---|
sin(x) |
OP_SIN |
Sine (radians) |
cos(x) |
OP_COS |
Cosine (radians) |
tan(x) |
OP_TAN |
Tangent (radians) |
asin(x) |
OP_ASIN |
Arc sine β radians |
acos(x) |
OP_ACOS |
Arc cosine β radians |
atan(x) |
OP_ATAN |
Arc tangent β radians |
sqrt(x) |
OP_SQRT |
Square root |
abs(x) |
OP_ABS |
Absolute value |
log(x) |
OP_LOG |
Natural logarithm (base e) |
floor(x) |
OP_FLOOR |
Round down to integer |
ceil(x) |
OP_CEIL |
Round up to integer |
deg(x) |
OP_DEG |
Radians β Degrees |
rad(x) |
OP_RAD |
Degrees β Radians |
exp(x) |
OP_EXP |
e^x (exponential) |
Binary Math Functions (2 Arguments)
These functions take two numeric arguments:
| Function | Opcode | Description |
|---|---|---|
atan2(y, x) |
OP_ATAN2 |
Arc tangent of y/x (full circle) |
pow(base, exp) |
OP_POW |
base^exp (exponentiation) |
Examples
Physics - Projectile Motion
// Calculate projectile trajectory
def calculate_trajectory(velocity, angle_deg, time)
{
var angle = rad(angle_deg); // OP_RAD
var vx = velocity * cos(angle); // OP_COS
var vy = velocity * sin(angle); // OP_SIN
var gravity = -9.81;
var x = vx * time;
var y = vy * time + 0.5 * gravity * pow(time, 2); // OP_POW
return {x: x, y: y};
}
var pos = calculate_trajectory(50, 45, 1.0);
write("Position: ({}, {})\\n", pos.x, pos.y);
AI - Angle to Target
// Calculate angle from enemy to player
def angle_to_target(enemy_x, enemy_y, player_x, player_y)
{
var dx = player_x - enemy_x;
var dy = player_y - enemy_y;
// atan2 returns angle in radians (-PI to PI)
var angle_rad = atan2(dy, dx); // OP_ATAN2
var angle_deg = deg(angle_rad); // OP_DEG
return angle_deg;
}
var angle = angle_to_target(100, 100, 200, 150);
write("Enemy should face: {} degrees\\n", angle);
Math - Distance Formula
// Calculate distance between two points
def distance(x1, y1, x2, y2)
{
var dx = x2 - x1;
var dy = y2 - y1;
// sqrt(dxΒ² + dyΒ²)
return sqrt(pow(dx, 2) + pow(dy, 2)); // OP_SQRT, OP_POW
}
var dist = distance(0, 0, 3, 4);
write("Distance: {}\\n", dist); // 5.0
Graphics - Circular Motion
// Object moving in a circle
process circular_motion()
{
var angle = 0;
var radius = 100;
var center_x = 400;
var center_y = 300;
loop
{
var x = center_x + radius * cos(rad(angle)); // OP_RAD, OP_COS
var y = center_y + radius * sin(rad(angle)); // OP_RAD, OP_SIN
write("Position: ({}, {})\\n", floor(x), floor(y)); // OP_FLOOR
angle = angle + 2; // 2 degrees per frame
if (angle >= 360) angle = 0;
frame;
}
}
circular_motion();
- Math functions compile to single opcodes - no function call overhead
- Physics calculations run at near-native speed
- Critical for real-time game loops processing thousands of entities
- Comparable performance to compiled C/C++ math operations
Functions
Functions in BuLang are first-class citizens. You can define them and pass them as arguments:
// Function definition
def add(a, b) {
return a + b
}
result = add(5, 3)
print(result) // 8
def apply(func, x, y) {
return func(x, y)
}
print(apply(add, 10, 20)) // 30
def fib(n)
{
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
var start = clock();
fib(31);
var end = clock();
var ms = end - start;
assert(ms < 50, "fib perf");
pass("performance");
Fibers
def patrol() {
var count = 0;
while (count < 10) {
write("Patrol tick {}\n", count);
yield(100); // Suspende 100ms
count++;
}
print("Patrol done!");
}
def attack() {
var count = 0;
while (count < 5)
{
write("Attack tick {}\n", count);
yield(200); // Suspende 200ms
count++;
}
print("Attack done!");
}
process enemy() {
x = 100;
fiber patrol(); // Fiber 1
fiber attack(); // Fiber 2
// Fiber 0 (main) continua aqui
var i = 0;
while (i < 2000) {
// write("Main fiber: x={}\n", x);
x++;
frame;
i++;
}
}
struct State {
var x, y;
};
def teste(state)
{
while (true)
{
print("A");
state.x = state.x + 1;
yield(100);
print("B");
state.y = state.y + 1;
yield(100);
}
}
process enemy2()
{
var state = State(1, 1);
fiber teste(state);
while (true)
{
write("{} {}\n", state.x, state.y);
frame;
}
}
enemy2();
enemy();
Typed Buffers
BuLang provides typed buffers for efficient binary data manipulation. Buffers are essential for image processing, audio, file I/O, and interfacing with low-level APIs.
Typed buffers bring zero-copy operations, direct file I/O, and type-safe binary data handling to BuLang.
Creating Buffers
Use the @(size, type) syntax to create typed buffers:
// Create buffers of different types
var bytes = @(1024, TYPE_UINT8); // 1KB byte buffer
var shorts = @(100, TYPE_INT16); // 100 signed 16-bit integers
var ints = @(1000, TYPE_INT32); // 1000 signed 32-bit integers
var floats = @(256, TYPE_FLOAT); // 256 floats
// Load from file
var image_data = @("texture.raw", TYPE_UINT8);
print(image_data.length()); // Size in bytes
Type System
BuLang supports four buffer types:
| Type | Size | Range | Use Case |
|---|---|---|---|
TYPE_UINT8 |
1 byte | 0 to 255 | Pixels, bytes, RGB |
TYPE_INT16 |
2 bytes | -32,768 to 32,767 | Audio samples |
TYPE_INT32 |
4 bytes | -2.1B to 2.1B | Large integers |
TYPE_FLOAT |
4 bytes | Β±3.4e38 | Coordinates, math |
Buffer Methods
Indexing and Assignment
// Access elements like arrays
var buffer = @(10, TYPE_UINT8);
buffer[0] = 255;
buffer[1] = 128;
buffer[2] = 64;
print(buffer[0]); // 255
print(buffer[1]); // 128
fill(value) - Fill Buffer
Fill entire buffer with a single value:
// Fill all elements
var floats = @(100, TYPE_FLOAT);
floats.fill(3.14159);
print(floats[0]); // 3.14159
print(floats[99]); // 3.14159
clear() - Zero Buffer
Set all elements to zero:
// Clear buffer
var data = @(50, TYPE_INT32);
data.fill(42);
data.clear();
print(data[0]); // 0
length() - Get Size
Returns the number of elements in the buffer:
// Get buffer size
var buffer = @(256, TYPE_UINT8);
print(buffer.length()); // 256
slice(start, end) - Create View
Create a view into a subset of the buffer (no copy):
// Create buffer view
var original = @(10, TYPE_INT32);
original.fill(100);
// View elements [2, 3, 4]
var view = original.slice(2, 5);
print(view.length()); // 3
// Modifying view affects original
view.fill(999);
print(original[2]); // 999
copy(dst_offset, src, src_offset, count) - Copy Data
Copy data between buffers:
// Copy between buffers
var src = @(5, TYPE_UINT8);
var dst = @(10, TYPE_UINT8);
// Fill source
for (var i = 0; i < 5; i = i + 1) {
src[i] = i * 10;
}
// Copy src[0:5] to dst[3:8]
dst.copy(3, src, 0, 5);
// dst = [0, 0, 0, 0, 10, 20, 30, 40, 0, 0]
save(filename) - Save to File
Write buffer contents to a binary file:
// Save buffer to disk
var pixels = @(1920 * 1080 * 4, TYPE_UINT8);
pixels.fill(128); // Gray image
pixels.save("output.raw");
Buffer Examples
Image Processing
// Create RGB gradient (100x100)
var width = 100;
var height = 100;
var image = @(width * height * 3, TYPE_UINT8);
// Fill with gradient
for (var y = 0; y < height; y = y + 1) {
for (var x = 0; x < width; x = x + 1) {
var i = (y * width + x) * 3;
image[i + 0] = x * 2; // R
image[i + 1] = y * 2; // G
image[i + 2] = 128; // B (constant)
}
}
// Save as raw RGB
image.save("gradient.raw");
// Convert with ImageMagick:
// $ convert -size 100x100 -depth 8 rgb:gradient.raw output.png
Audio Buffer
// Generate 440Hz sine wave (1 second)
var sample_rate = 44100;
var audio = @(sample_rate, TYPE_INT16);
var frequency = 440.0;
for (var i = 0; i < sample_rate; i = i + 1) {
var t = i / sample_rate;
var sample = sin(2.0 * 3.14159 * frequency * t) * 32767.0;
audio[i] = sample;
}
audio.save("tone_440hz.raw");
Particle System
// 10,000 particles (x, y, vx, vy)
var num_particles = 10000;
var particles = @(num_particles * 4, TYPE_FLOAT);
// Initialize
for (var i = 0; i < num_particles; i = i + 1) {
var base = i * 4;
particles[base + 0] = random() * 800; // x
particles[base + 1] = random() * 600; // y
particles[base + 2] = (random() - 0.5) * 2; // vx
particles[base + 3] = (random() - 0.5) * 2; // vy
}
Type Overflow Behavior
// Automatic wrapping for unsigned types
var b8 = @(4, TYPE_UINT8);
b8[0] = 255; // OK
b8[1] = -1; // Wraps to 255
b8[2] = 256; // Wraps to 0
b8[3] = 300; // Wraps to 44 (300 % 256)
print(b8[0]); // 255
print(b8[1]); // 255
print(b8[2]); // 0
print(b8[3]); // 44
- Use
slice()for views instead of copying - Batch operations with
fill()instead of loops - Binary I/O is much faster than text serialization
- Buffers provide zero-copy FFI for C/C++ integration
Classes & Object-Oriented Programming
BuLang supports class-based OOP with methods and inheritance:
// Class definition
class Player
{
def init(name, health)
{
this.name = name
this.health = health
}
def take_damage(amount)
{
this.health = this.health - amount
}
def heal(amount) {
this.health = this.health + amount
}
def status() {
print(this.name + " - HP: " + this.health)
}
}
// Using classes
player = Player.new("Hero", 100)
player.take_damage(20)
player.status() // Hero - HP: 80
class Builder {
var x, y;
def init() {
self.x = 0;
self.y = 0;
}
def setX(v) {
self.x = v;
return self;
}
def setY(v) {
self.y = v;
return self;
}
}
var b = Builder();
b.setX(10).setY(20);
print(b.x); // 10
print(b.y); // 20
class A {
var a;
def init() {
print("A.init()");
self.a = 1;
}
def show() { print("Level A: " + self.a); }
}
class B : A {
var b;
def init() {
print("B.init()");
super.init();
self.b = 2;
}
def show() {
print("Level B: " + self.b);
super.show();
}
}
class C : B {
var c;
def init() {
print("C.init()");
super.init();
self.c = 3;
}
def show() {
print("Level C: " + self.c);
super.show();
}
}
class D : C {
var d;
def init() {
print("D.init()");
super.init();
self.d = 4;
}
def show() {
print("Level D: " + self.d);
super.show();
}
}
class E : D {
var e;
def init() {
print("E.init()");
super.init();
self.e = 5;
}
def show() {
print("Level E: " + self.e);
super.show();
}
}
class F : E {
var f;
def init() {
print("F.init()");
super.init();
self.f = 6;
}
def show() {
print("Level F: " + self.f);
super.show();
}
}
print("Creating F...");
var f = F();
print("\nCalling f.show():");
f.show();
print("\nAccessing all fields:");
print("f.a = " + f.a);
print("f.b = " + f.b);
print("f.c = " + f.c);
print("f.d = " + f.d);
print("f.e = " + f.e);
print("f.f = " + f.f);
Processes - Cooperative Multitasking
BuLang's unique process system allows you to handle thousands of concurrent tasks efficiently:
// Process example
process enemy_behavior(enemy_id) {
while true {
// Do something
print("Enemy " + enemy_id + " acting")
// Yield to next frame
frame
// Decision making
if some_condition {
break
}
}
}
// Spawn processes
enemy_behavior(1)
enemy_behavior(2)
enemy_behavior(3)
Module System
BuLang features a revolutionary module system that achieves zero-overhead native function calls through compile-time resolution and advanced bit-packing techniques.
Module Basics
BuLang provides two ways to use modules: import (namespaced) and using (flat).
Import - Namespaced Access
Import brings modules into scope but requires prefixed access:
// Import with namespace
import timer;
import net;
// Use with prefix
var t = timer.create(5.0);
var socket = net.connect("server", 8080);
if (timer.elapsed(t)) {
print("Time's up!");
}
Using - Flat Access
Using brings module functions into the global scope for convenient access:
// Using for flat access
import raylib;
using raylib; // Must import first!
// Now use without prefix
InitWindow(800, 600, "Game");
SetTargetFPS(60);
while (!WindowShouldClose()) {
BeginDrawing();
ClearBackground(BLACK);
DrawText("Hello!", 10, 10, 20, WHITE);
EndDrawing();
}
Multiple Imports
Import or use multiple modules at once:
// Multiple imports
import timer, net, fs;
using raylib, math;
// Raylib and math are flat
InitWindow(800, 600, "Game");
var angle = math.sin(math.PI / 2);
// Timer, net, fs use prefix
var t = timer.create(1.0);
var data = fs.read("config.json");
Import All (*)
Import all available modules at once:
// Import all modules
import *;
// All modules available with prefix
var t = timer.create(1.0);
var socket = net.connect("localhost", 8080);
Module Constants
Modules can export constants like numbers, strings, and booleans:
// Using module constants
import math;
var circle = math.PI * 2; // 6.28...
var half_pi = math.PI / 2; // 1.57...
print(math.E); // 2.71828...
print(math.MAX_INT); // 2147483647
Hybrid Approach
Combine import and using for optimal organization:
// Organize by usage frequency
import timer, net, fs, json;
using raylib, math;
// High-frequency: flat (clean!)
InitWindow(800, 600, "Game");
var angle = sin(0.5);
// Low-frequency: namespaced (organized!)
var t = timer.create(5.0);
var data = json.parse(fs.read("config.json"));
var socket = net.connect("server", 8080);
Performance
The module system achieves exceptional performance through several innovations:
| Feature | Implementation | Benefit |
|---|---|---|
| ModuleRef | 32-bit value (16+16) | Direct array indexing, zero hash lookups |
| Tree Shaking | Compile-time analysis | Only register functions actually used (~80% savings) |
| Constants | Inline at compile time | No runtime lookup, immediate values |
| Resolution | Compile-time ID mapping | Zero string operations at runtime |
Performance Example
// Tree shaking in action
// C++ side: Define 20 timer functions
vm.defineModule("timer")
->addFunction("create", ..., 1)
->addFunction("elapsed", ..., 1)
->addFunction("reset", ..., 1)
->addFunction("pause", ..., 1)
// ... 16 more functions
// BuLang side: Use only 2
import timer;
var t = timer.create(5.0);
if (timer.elapsed(t)) {
print("Done!");
}
// Result: Only 2 functions registered!
// 18 functions never loaded β 90% memory saving!
Multi-Backend Support
The module system enables clean separation of different backends:
// Support multiple graphics libraries
import raylib;
import sdl;
var useRaylib = true;
if (useRaylib) {
using raylib;
InitWindow(800, 600, "Raylib Game");
} else {
using sdl;
CreateWindow("SDL Game", 800, 600);
}
// Each backend has independent namespace!
// No function name conflicts!
Defining Modules (C++)
Create modules on the C++ side using the fluent API:
// C++ module definition
// main.cpp
Interpreter vm;
// Define timer module
vm.defineModule("timer")
->addFunction("create", native_timer_create, 1)
->addFunction("elapsed", native_timer_elapsed, 1)
->addFunction("reset", native_timer_reset, 1);
// Define math module
vm.defineModule("math")
->addDouble("PI", 3.14159265358979)
->addDouble("E", 2.71828182845905)
->addInt("MAX_INT", 2147483647)
->addFunction("sin", native_math_sin, 1)
->addFunction("cos", native_math_cos, 1)
->addFunction("sqrt", native_math_sqrt, 1);
// Compile and run
vm.compile("game.bu");
vm.run();
Best Practices
1. Use 'using' for High-Frequency Calls
// Graphics engine: many calls
using raylib;
// Clean game loop
while (!WindowShouldClose()) {
BeginDrawing();
ClearBackground(BLACK);
DrawCircle(100, 100, 50, RED);
EndDrawing();
}
2. Use 'import' for Low-Frequency or Clarity
// Utility modules: few calls
import timer;
import fs;
// Clear which module each comes from
var t = timer.create(1.0);
var data = fs.read("config.json");
3. Organize Imports at Top
// File organization
// Imports first
import timer, net, fs, json;
// Using for frequently used
using raylib, math;
// Constants
var SCREEN_WIDTH = 800;
var SCREEN_HEIGHT = 600;
// Functions
def gameLoop() {
// ...
}
// Main logic
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Game");
gameLoop();
Error Handling
The module system provides comprehensive compile-time validation:
// Error examples
// Error: Module not defined
import invalid_module;
// Compile error: Module 'invalid_module' not defined
// Error: Using without import
using timer;
// Compile error: Module 'timer' not imported. Use 'import timer;' first
// Error: Function not found
import timer;
timer.invalid_function();
// Compile error: 'invalid_function' not found in module 'timer'
// Error: Assigning to constant
import math;
math.PI = 3;
// Compile error: Invalid assignment target
Technical Details
ModuleRef Structure
Internally, module references use 32-bit integers:
// Bit layout
ββββββββββββββββββββ¬βββββββββββββββββββ
β 16 bits β 16 bits β
β Module ID β Function ID β
ββββββββββββββββββββ΄βββββββββββββββββββ
// Example: timer.create()
Module ID: 5 (0x0005)
Function ID: 2 (0x0002)
PackedValue: 0x00050002
// Runtime resolution:
modules[5].functions[2]() // Direct array access!
Compilation Pipeline
// How module calls are compiled
BuLang Code: timer.create(1.0)
β
Parse: identifier("timer") . identifier("create") (args)
β
Resolve: moduleId=5, funcId=2
β
Pack: value = (5 << 16) | 2 = 0x00050002
β
Emit: LOAD_CONST 0x00050002
LOAD_CONST 1.0
CALL 1
β
Runtime: modules[5].functions[2](vm, 1, args)
Memory Comparison
| Approach | Storage | Lookup | Speed |
|---|---|---|---|
| HashMap (old) | String + pointer per function | Hash + strcmp | ~100ns |
| ModuleRef (new) | 32-bit integer | Direct array index | ~1ns |
Complete Examples
Game with Timer and Graphics
// Complete game example
import timer;
using raylib;
// Setup
InitWindow(800, 600, "BuLang Game");
SetTargetFPS(60);
var gameTimer = timer.create(10.0);
var score = 0;
// Game loop
while (!WindowShouldClose()) {
// Update
if (timer.elapsed(gameTimer)) {
score++;
timer.reset(gameTimer);
}
// Draw
BeginDrawing();
ClearBackground(BLACK);
DrawText("Score: " + score, 10, 10, 20, WHITE);
DrawText("Time-based game!", 10, 40, 16, GRAY);
EndDrawing();
}
CloseWindow();
Network Client
// Network example
import net, timer, json;
// Connect to server
var socket = net.connect("game.server.com", 8080);
if (socket != nil) {
// Send player data
var playerData = {
name: "Player1",
level: 5
};
var jsonData = json.stringify(playerData);
net.send(socket, jsonData);
// Wait for response
var timeout = timer.create(5.0);
while (!timer.elapsed(timeout)) {
var response = net.receive(socket);
if (response != nil) {
var data = json.parse(response);
print("Server says: " + data.message);
break;
}
}
net.close(socket);
}
Math-Heavy Simulation
// Physics simulation
using math; // Flat for frequent use
class Particle {
var x, y, vx, vy;
def init(x, y) {
self.x = x;
self.y = y;
self.vx = cos(random() * PI * 2);
self.vy = sin(random() * PI * 2);
}
def update(dt) {
// Use math functions without prefix
self.x += self.vx * dt;
self.y += self.vy * dt;
// Bounce off walls
if (self.x < 0 || self.x > 800) {
self.vx = -self.vx;
}
if (self.y < 0 || self.y > 600) {
self.vy = -self.vy;
}
}
}
// Create particles
var particles = [];
for (var i = 0; i < 100; i++) {
particles.push(Particle(400, 300));
}
// Update loop
for (var i = 0; i < particles.length(); i++) {
particles[i].update(0.016);
}
Raylib Integration
BuLang integrates seamlessly with Raylib for 2D/3D graphics:
// Simple Raylib game loop
def main()
{
InitWindow(800, 600, "BuLang Game");
SetTargetFPS(60);
var red = Color(255,0,0,255); // native raylib color
var blue = Color(0,0,255,255);
while not WindowShouldClose()
{
BeginDrawing();
ClearBackground(blue);
// Draw rectangle
DrawRectangle(100, 100, 50, 50, red);
EndDrawing();
}
CloseWindow();
}
main()
API Reference
Core Functions
| Function | Description |
|---|---|
| frame | Pause process until next update |
| yield | Pause function until next update |