Skip to content

IO

Blombly offers ways to interact with the file system and web resources. Expect more options in the future. In addition to the basic operations described here, find supporting functions in libs.

Warning

Never expect IO data to persist: not only could they change externally, but Blombly may even parallelize the operations (those are still atomic for safety).

Permissions

If you run IO operations out-of-the-box, you may encounter an error like below. This happens because Blombly prioritizes execution safety and prevents tampering with or exposing system resources without explicitly stating this intent. By default, the virtual machine has read access rights for bb://libs (the contents of its standard library next to the executable) and the working directory. The latter is the place from where you call the executable, such as the the path you have cd-ed in your terminal.


Without permissions, Blombly cannot modify anything and cannot read from anywhere else. This ensures that the one running the virtual machine is always in control of effects on their device. For example, this limits preprocessor directives in included files from escaping from the intended build system. Normal safety features from your operating system also apply externally, but this is how the language ensures that its programs are as safe as they can get.

// main.bb
f = file("../README.md");
print(f);
> ./blombly main.bb
( ERROR ) Access denied for path: ../README.md
   !!! This is a safety measure imposed by Blombly.
       You need to add read permissions to a location containing the prefix with `!access "location"`.
       Permissions can only be granted this way from the virtual machine's entry point.
       They transfer to all subsequent running code as well as to all following `!comptime` preprocessing.
     file("README.md")                                   playground/main.bb line 1

Blombly permissions can only be declared on the file that is directly executed; they will create errors if declared elsewhere without already being allowed. There are two kinds of permissions; read access with the !access @str directive, and read and write access with the !modify @str directive. Resource paths can only extend the provided strings, but multiple permissions may be declared.


Permissions apply everywhere for the running language instance. For example, they restrict include directives, and code executed during compilation. But they do not carry over to new runs, for example of .bbvm files. Those do not preserve permissions to let people running programs protect themselves against unintended consequences. You

// permissions.bb
// grants read permissions to the higher-level directory
!access "../"
// main.bb
// permissions here are available only when compiling and running the .bb file
!access "../"
print("../README.md"|file|str|len);
# compilation without running
> ./blombly main.bb  --norun
# option 1: directly run permission code
> ./blombly '!access "../"' main.bbvm
# option 2: run permission file
> ./blombly permissions.bb main.bbvm

FIles

The main way blombly interacts with the file system is through the file data type. This is an abstraction over resources like files, directories, and web data obtained with http. Path meaning and resolution depends on their prefix, as described in the second of the drop-down tables below. Operations applicable to file data include pushing data to resources and iterating through retrieved resource data are summarized here. Of those operations, pushing is applicable only to files and overwrites their text contents with the pushed string.

Operations
Operation Description
<< Push data to overwrite the resource without expecting persistence. May return a reply.
iter Traverse through resource contents (used by the in macro internally).
list Obtain all contents of the resource at once.
clear Clear the resource data if possible.
bool Check whether the resource exists.
Division by a string Obtain a sub-directory from the resource.
Special path prefixes
Prefix Description
http:// Refers to a resource accessible over the HTTP protocol.
https:// Refers to a resource accessible over the HTTPS protocol.
ftp:// Refers to a files accessible over the FTP protocol.
ftps:// Refers to a files accessible over the FTPS protocol.
sftp:// Refers to a files accessible over the SFTP protocol (FTP over SSH).
bb:// Starts from the directory where the Blombly executable resides.
raw:// Do not modify the provided path (this is not the default for safety).
vfs:// Accesses the path on a virtual file system. This is lost when the interpreter exits but persists throughout all compilation.
Empty path Used to grant permissions for everything (NOT RECOMMENDED)
No prefix Starts from the user's working directory.

Iterating through file contents yields the read lines. Whereas iterating through directories yields their contents. You cannot push to directories, and -for safety- can only clear empty directories. Here is an example for reading from the local file system, as well as checking whether a non-existing file name exists:

!access "" // read access to all resources (NOT RECOMMENDED)

f = file("../README.md");
while(line in f) print(line);
print("nonexisting filename"|file|bool); // false

Web resources

Web resources are read in the same way given the prefixes for the protocols: http, https, ftp, sftp, ftps, sftp. The first two of those protocols do not support pushing to the resource but the rest do. All resource access errors can be caught normally, as described here.

// main.bb
// grant necessary read access rights
!access "https://" 
start = time();

// join the string contents of the file iterable
response = "https://www.google.com"|file|bb.string.join("\n");

print("Response length: !{response|len}");
print("Response time: !{time()-start} sec");
> ./blombly main.bb 
Response length: 55078 
Response time: 0.319413 sec 

Authentication

For web resources, you can set authentication and timeout parameters with a notation similar to setting struct fields. Once set, these parameters cannot be retrieved and do not appear in any error messages. Below is an example, in which set username and password with credentials from sftpcloud's free SFTP server - create a testing server for one hour with just a click and without logging in.

!modify "sftp://"

// new server for 1 hour at: https://sftpcloud.io/tools/free-ftp-server
username = "myusername";
password = "mypassword";

// create file from sftp
sftp = "sftp://eu-central-1.sftpcloud.io/test_file"|file;
sftp.io::username = username;
sftp.io::password = password; 
sftp.io::timeout = 10;
sftp << "Transferred data.";

// retrieve file from sftp
sftp = "sftp://eu-central-1.sftpcloud.io/test_file"|file;
sftp.io::username = username;
sftp.io::password = password; 
sftp.io::timeout = 10;
print(ftp|bb.string.join("\n"));
> ./blombly main.bb 
Transferred data.

Info

Resources do not accept the struct field set notation but only map set notation. Field-like notation is made available through standard library macros, for example to replace .io::timeout with ["timeout"].

Servers

Blombly offers the ability to set up REST services. Instantiating a server is as simple as calling routes=server(port), where the port is provided. The server starts running immediately, and you can dynamically add or remove routes from it. Execute client request through the file system, as described above.


Treat the generated server as a map from resource location strings to code blocks to be called when respective resources are requested. Blocks that run this way should return either a string plain text or a request result struct (see below). Parts of resource names that reside in angular brackets <...> indicate that a part of therequest should be treated as a string argument to the callable. For example, the following snippet redirects echo/<input> to echo the provided input; run the code and open the browser and visit localhost:8000/echo/MyTest to see this in action.

// main.bb
routes = server(8000);
routes["/echo/<input>"] => input; // equivalent to `... = {return input}`
while(true) {}  // wait indefinitely

In addition to parameters obtained by parsing the request, calls to routes may be enriched with status information, if available. Related values that may be present are listed below.

Status information
Value Type Details
method str "GET" or "POST".
content str Any received message content. This requires further parsing.
ip str The user's IP address.
http str The HTTP protocol's version.
query str Any query parameters; those following after the question mark (?).
ssl bool Whether HTTPS or WS is used (SSL/TLS used).
uri str The request's full URI.

Non-text results for server methods are declared by returning a struct with text and type fields, like in the following example. In general, rout functions are called from ther server's creating scope. So in the example content is made final to be visible from the top-level route.

final content = "
    <!DOCTYPE html>
    <html>
        <head><title>Hi world!</title></head>
        <body><h1>Hi world!</h1>This is your website. 
        Add content in a <a href='https://perfectmotherfuckingwebsite.com/'>nice format</a>.</body>
    </html>";

routes = server(8000);
routes[""] => new {
    str => this..content; // definition time closure
    type = "text/html";
}

print("Server running at http://localhost:8000/");
while(true){}

Info

Servers run in their creating scope. They stop and create errors when removed from any scope, though, to promote unique ownership rules. To avoid errors, stop servers with clear(routes), or move them to run in different execution scopes with move(routes).

Databases

Blombly wraps the sqlite database connector, which stores data in the file system. Initialize a database with a string denoting a file location. An empty string creates a temporary file to be deleted when closed, and ":memory:" creates an in-memory instance without persistence. Like before, databases require appropriate permissions to access the file system. After initializing them, push string operations. Each operation returns a list that contains maps from column names to string values. Below is an example that creates and iterates through a list of users.

db = sqlite(":memory:");
db << "PRAGMA journal_mode = 'wal';"; // often speeds things up (https://www.sqlite.org/wal.html)
db << "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);";

while(i in range(5)) {
    db << "BEGIN TRANSACTION;"; // instead of this, prefer batching operations into larger transactions
        db << "INSERT INTO users (name, age) VALUES ('User{i}', {20 + (i % 10)});";
        db << "SELECT * FROM users WHERE id = {i};";
        db << "UPDATE users SET age = age + 1 WHERE id = {i};";
        //db["DELETE FROM users WHERE id = {i};"]; // deletes are generally slow
    db << "COMMIT;";
}

while(user in db<<"SELECT * FROM users;") print(user);
db << "DELETE FROM users;";
> ./blombly main.bb
{name: User0, age: 21, id: 1} 
{name: User1, age: 22, id: 2} 
{name: User2, age: 23, id: 3} 
{name: User3, age: 24, id: 4} 
{name: User4, age: 24, id: 5} 

Graphics

Blombly provides a graphics engine based on SDL2. The main mechanism consists of initializing a graphics window and then either pushing display data onto it, or popping from it. The last operation yields a list of graphics events related to keys and the mouse, or create an out-of-bounds error in case an exit signal is given to the window.


Below is an example demonstrating interaction with graphics. Mainly, text can be pushed in the form of of a list comprising a string, a font file name (permission rules apply), the font size, the coordinates, and an angle rotation. Textures are displayed by providing a path, corrdinates, dimensions, and rotation. Blombly caches fonts and textures under the hood, so everything runs efficiently and irrespective of the language's actual speed.

g = graphics("MyApp", 800, 600);

logo = new {
    size = 200;
    x = 400-size/2;
    y = 300-size/2;
    speedx = 0;
    speedy = 0;
    angle = 0;
    update(dt) = {
        this.x += this.speedx*dt*100;
        this.y += this.speedy*dt*100;
        this.angle += 100*dt;
        return this;
    }
    texture() => "docs/blombly.png",this.x,this.y,this.size,this.size,this.angle;
}

font = "playground/fonts/OpenSans-VariableFont_wdth,wght.ttf";
invfps = 1/60;
previous_frame = time();
while(events as g|pop) {
    // draw graphics
    g << logo.texture();

    // show fps
    fps = (1/invfps)|int|str+" fps";
    g << fps,font,12,800-42,600-20,0;

    // process events
    while(event in events) if(event.io::type=="key::down") {
        if(event.io::key=="W") logo.speedy -= 1;
        if(event.io::key=="S") logo.speedy += 1;
        if(event.io::key=="A") logo.speedx -= 1;
        if(event.io::key=="D") logo.speedx += 1;
    }

    // update fps
    current_frame = time();
    dt = current_frame-previous_frame;
    previous_frame = current_frame;
    invfps = invfps*0.99+0.01*dt;

    // update loop (use try for forceful synchronization)
    logo = logo.update(dt);
}

Tip

For full UI capabilities, consider using the externally deployed uibb engine.

Warning

Multiple windows are not yet supported and it is unclear whether they will be supported in the future at all.