Skip to content

Libs

Blombly is packed alongside a libs directory that contains several out-of-the-box implementations. This directory is included by default for all running instances, including spawned !comptime directives. Its contents define several useful macros that have already been covered, as well as final bb struct with many helper functions. Here we exhaustively describe available functionality.

bb.ansi

Contains several ansi color codes under the colors namespace that you can use to colorize strings: black, lightblack, red, lightred, green, lightgreen, yellow, lightyello, blue, lightblue, purple, lightpurple, cyan, lightcyan, white, lightwhite, reset.

Here is an example, where the namespace is enabled to access variable names:

// main.bb
with colors:
print(bb.ansi.cyan+" INFO "+bb.ansi.reset+"This is a message.");
> ./blombly main.bb
> INFO This is a message.

bb.collection

Contains helper methods for list and map manipulation using the currying notation. Here is how to push to a list (or struct with the corresponding overloaded operation):

// main.bb
A = 1,2,3;
A |= bb.collection.toback(4);
print(A);
> ./blombly main.bb
1,2,3,4

bb.logger

Provides a standardized logging interface for messages. It implements the methods bb.ansi.log.fail(text), bb.ansi.log.info(text), bb.ansi.log.warn(text), bb.ansi.log.ok(text), which use ANSI to help colorize message.

bb.memory

Provides memory management options that supplement the default reference counting mechanism. Reference counting automatically clears any dangling memory, but does not explicitly handle cycles across struct references. For example, when the following snippet terminates, Blombly complains that there are two leaked memory contexts - tied to the respective structs A and B.

// main.bb
A = new{message="Hello world!"}
B = new{A=A}
A.B = B;
print(A.message);
> ./blombly main.bb
Hello world!
( ERROR ) There are 2 leftover memory contexts leaked

To break cycles like this, it suffices to remove one reference, for example by setting it to something else, like A.B=false. Or, to be safe, you can completely clear the context's internal variables to avoid keeping track of which exact fields. In the following snippet, we defer clearing one of the structs until the end of the parent scope. Clearing removes all data from a struct, but it is memory safe in that future access attempts create an error that can be handled.

// main.bb
A = new{message="Hello world!"}
defer clear(A); // there will be no leaks
B = new{A=A}
A.B = B;
print(A.message);
> ./blombly main.bb
Hello world!

To properly handle cyclic references in complex applications, create a bb.memory.raii() struct to hold a shared heap space. Push on this various structs to be automatically cleared. Below is an example where we again defer clearing to the end. The push operation returns the object being added in the memory.

// main.bb
memory = bb.memory.raii();
defer clear(memory);

A = memory<<new{message="Hello world!"}
B = memory<<new{A=A} // add at least one object in the memory to break the cyclic reference.
A.B = B;
print(A.message);
> ./blombly main.bb
Hello world!

bb.os

Automates several file handling tasks. In addition to those operations, filesystem access safety is imposed with the !access and !modify preprocessor directives described here. Access any system resources without explicitly providing the respective permissions to your program from your main/running file. For safety, required permissions found in any included files that are not a subset of those declared in the main program create an error and ask to be added.

!access "https://raw.githubusercontent.com/"
!modify "libs/download/"

bb.os.transfer("libs/download/html.bb", "https://raw.githubusercontent.com/maniospas/Blombly/refs/heads/main/libs/html.bb");

bb.json

Provides interfaces to convert to and from JSON respresentations. Parse strings into lists, maps, and other primitive types like so:

a = bb.os.read("test.json"); 
obj = bb.json.parse(a);
print(obj);

There are also helper methods for constructing JSON objects. Convert lists, maps, and strings to their appropriate counterparts as follows. Other data are converted to strings through the str() function. There is a lightweight sanity check that helps identify most missing conversions.

obj = bb.json.list(
    bb.json.map(("A", 2), ("B", "test"|bb.json.string)),
    bb.json.NULL
);
print(obj); // [{"A": 2,"B": "test"},"NULL"]

bb.sci

Provides a small data science package with plotting functionality over vectors. Plotting uses graphics under the hood but automates the event handling code. Just create a new struct with the appropriate code inlined, call plot to create interactive plots, and finally call show to see the result. You can overwrite sevreal defaults, also demonstrated below. For full UI capabilities, consider using the externally deployed uibb engine.

x = list();
y = list();
while(i in range(100)){
    x << i*0.01;
    y << ((i-50)*0.01)^2;
}
canvas = new{bb.sci.Plot:}
canvas = canvas.plot(x, y :: color=255,0,0,255; title="y=(x-0.5)^2");
canvas = canvas.plot(x, x :: color=0,255,0,255; title="y=x");
canvas.show(width=800;height=600);

bb.flat

Blombly’s design intentionally omits types for structs to stay flexible. However, in practice, bundling a few related values together is a common need when passing data between interfaces. Inspired by languages like Zig, bb.flat offers a way to bind small groups of values efficiently and have them look similar to structs without actually creating structs.


This is a zero-cost abstraction that allows you to name a sequence of comma-separated elements. Everything is resolved into local varaibles during compilation, eliminating most allocations.


Flat data types are declared using the !flat directive, followed by a name and argument names in a parenthessis - like a function signature without a body. To create variables of those types, prepend the assignment with the flat type’s name or put the type name before variable names in other types or function signatures. Once declared, those variables are automatically unpacked into individual elements, which can then be referenced using dot-notation (e.g., p.x, p.y) directly in the intermediate representation. Types are retained until the end of file, so use !include{...} to scope them. Use namespaces to borrow flat types.


Flat variables are passed by value to functions. Conceptually, they treat memory like a list of values, and the compiler does use this format to store them in the heap, for example in objects, lists, or maps. In the example bellow, the signature (Point a, Point b) is unpacked to (a.x, a.y, b.x, b.y) in the example below, where each one is treated as one name with the dot embedded insde instead of composite objects. This interoperability with normal arguments allows you to directly use comma-separated values when flats are expected.


Similarly, flat assignment syntax (e.g., Point p = 1,2;) ensures simple expansion to the underlying primitive types. Assignments like p2 = p1 merely copy lists of values without introducing tuple-specific metadata. But the compiler optimizes away symbols aggressively and performs any list operations it can reason about beforehand.

!flat Point(x,y);
!flat Array2D(0,1);
!flat Field(Point start, Array2D end);

adder(Point a, Point b) = {
    x = a.x+b.x;
    y = a.y+b.y;
    return x,y;
}

Point p1 = 1,2;
Point p2 = p1;
Point p3 = adder(p1, p2);
print(p3.x);
print(p3);

Field f = p1,adder(p1,p2);
print(f.start);
print(f.start.y);
print(f.end.1);