LoLa Programming Language
LoLa is a small programming language meant to be embedded into games to be programmed by the players. The compiler and runtime are implemented in Zig and C++.
Short Example
var list = [ "Hello", "World" ];
for(text in list) {
Print(text);
}
You can find more examples in the examples folder.
Why LoLa when there is X?
LoLa isn't meant to be your next best day-to-day scripting language. Its design is focused on embedding the language in environments where the users want/need/should write some small scripts like games or scriptable applications. In most script languages, you as a script host don't have control over the execution time of the scripts you're executing. LoLa protects you against programming errors like endless loops and such:
Controlled Execution Environment
Every script invocation gets a limit of instructions it might execute. When either this limit is reached or the script yields for other reasons (asynchronous functions), the execution is returned to the host.
This means, you can execute the following script "in parallel" to your application main loop without blocking your application and without requiring complex multithreading setups:
var timer = 0;
while(true) {
Print("Script running for ", timer, " seconds.");
timer += 1;
Sleep(1.0);
}
Native Asynchronous Design
LoLa features both synchronous and asynchronous host functions. Synchronous host function calls are short-lived and will be executed in-place. Asynchronous functions, in contrast, will be executed multiple times until they yield a value. When they don't yield a value, control will be returned to the script host.
This script will not exhaust the instruction limit, but will only increment the counter, then return control back to the host:
var counter = 0;
while(true) {
counter += 1;
Yield();
}
This behaviour can be utilized to wait for certain events in the host environment, for example to react to key presses, a script could look like this:
while(true) {
var input = WaitForKey();
if(input == " ") {
Print("Space was pressed!");
}
}
Note that the current implementation is not thread-safe, but requires to use the limited execution for running scripts in parallel.
Native "RPC" Design
LoLa also allows executing multiple scripts on the same environment, meaning that you can easily create cross-script communications:
// script a:
var buffer;
function Set(val) { buffer = val; }
function Get() { return val; }
// script b:
// GetBuffer() returns a object referencing a environment for "script a"
var buffer = GetBuffer();
buffer.Set("Hello, World!");
// script c:
// GetBuffer() returns a object referencing a environment for "script a"
var buffer = GetBuffer();
Print("Buffer contains: ", buffer.Get());
With a fitting network stack and library, this can even be utilized cross-computer.
This example implements a small chat client and server that could work with LoLa RPC capabilities:
// Chat client implementation:
var server = Connect("lola-rpc://random-projects.net/chat");
if(server == void) {
Print("Could not connect to chat server!");
Exit(1);
}
while(true) {
var list = server.GetMessages(GetUser());
for(msg in list) {
Print("< ", msg);
}
Print("> ");
var msg = ReadLine();
if(msg == void)
break;
if(msg == "")
continue;
server.Send(GetUser(), msg);
}
// Chat server implementation
var messages = CreateDictionary();
function Send(user, msg)
{
for(other in messages.GetKeys())
{
if(other != user) {
var log = messages.Get(other);
if(log != void) {
log = log ++ [ user + ": " + msg ];
} else {
log = [];
}
messages.Set(other, log);
}
}
}
function GetMessages(user)
{
var log = messages.Get(user);
if(log != void) {
messages.Set(user, []);
return log;
} else {
return [];
}
}
Serializable State
As LoLa has no reference semantics except for objects, it is easy to understand and learn. It is also simple in its implementation and does not require a complex garbage collector or advanced programming knowledge. Each LoLa value can be serialized/deserialized into a sequence of bytes (only exception are object handles, those require some special attention), so saving the current state of a environment/vm to disk and loading it at a later point is a first-class supported use case.
This is especially useful for games where it is favourable to save your script state into a save game as well without having any drawbacks.
Simple Error Handling
LoLa provides little to no in-language error handling, as it's not designed to be robust against user programming errors. Each error is passed to the host as a panic, so it can show the user that there was an error (like OutOfMemory
or TypeMismatch
).
In-language error handling is based on the dynamic typing: Functions that allow in-language error handling just return void
instead of a actual return value or true
/false
for success or failure.
This allows simple error checking like this:
var string = ReadFile("demo.data");
if(string != void) {
Print("File contained ", string);
}
This design decision was made with the idea in mind that most LoLa programmers won't write the next best security critical software, but just do a quick hack in game to reach their next item unlock.
Smart compiler
As LoLa isn't the most complex language, the compiler can support the programmer. Even though the language has fully dynamic typing, the compiler can do some type checking at compile time already:
// warning: Possible type mismatch detected: Expected number|string|array, found boolean
if(a < true) { }
Right now, this is only used for validating expressions, but it is planned to extend this behaviour to annotate variables as well, so even more type errors can be found during compile time.
Note that this is a fairly new feature, it does not catch all your type mismatches, but can prevent the obvious ones.
Starting Points
To get familiar with LoLa, you can check out these starting points:
When you want to contribute to the compiler, check out the following documents:
Visual Studio Code Extension
If you want syntax highlighting in VSCode, you can install the lola-vscode
extension.
Right now, it's not published in the gallery, so to install the extension, you have to sideload it. See the VSCode documentation for this.
Building
Continous Integration
Requirements
- The Zig Compiler (Version 0.6.0+12ce6eb8f or newer)
Building
zig build
./zig-cache/bin/lola
Examples
To compile the host examples, you can use zig build examples
to build all provided examples. These will be available in ./zig-cache/bin
then.
Running the test suite
When you change things in the compiler or VM implementation, run the test suite:
zig build test
This will execute all zig tests, and also runs a set of predefined tests within the src/test/
folder. These tests will verify that the compiler and language runtime behave correctly.
Building the website
The website generator is gated behind -Denable-website
which removes a lot of dependencies for people not wanting to render a new version of the website.
If you still want to update/change the website or documentation, use the following command:
zig build -Denable-website "-Dversion=$(git describe --tags || git rev-parse --short HEAD)" website
It will depend on koino, which is included as a git submodule. Adding new pages to the documentation is done by modifying the menu_items
array in src/tools/render-md-page.zig
.