Skip to content
torkeliciousPublic

About

A small, interpreted, thread-safe, dynamically-typed scripting language for game logic in the Obliberry Game Engine, designed to be easily extendable.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

72 Commits

Folders and files

Repository files navigation

The Obliberry Scripting Language

ObSL is a small interpreted, thread safe, dynamically typed programming language designed for game logic inside the
Obliberry Game Engine, made to be easily extendable.


Contents


Documentation


Features

  • Dynamically typed variables
  • Functions with default parameters
  • Closures and first-class functions
  • Structs with custom constructors
  • Dynamic objects
  • Dynamic arrays with negative indexing
  • Module importing
  • Exception handling
  • Runtime reflection
  • Built-in regex support
  • Automatic garbage collection with cyclic reference handling

Building from Source

Prerequisites

  • C++20 compatible compiler (GCC, Clang, MSVC)
  • CMake
  • Git

Clone the Repository

git clone https://github.com/torkelicious/ObSL.git
cd ObSL

Build the Project

mkdir build
cd build
cmake ..
cmake --build .

CMake Options

Option Default Description
OBSL_BUILD_RUNTIME ON Builds the obsl_runtime CLI executable. Set to OFF to build only the obsl static library useful when embedding ObSL into another project (e.g. the Obliberry Game Engine) that doesn't need the standalone CLI/REPL.
OBSL_BUILD_FORMATTER ON Builds the obsl_fmt CLI executable. Set to OFF if you don't intend to use the formatter.
cmake .. -DOBSL_BUILD_RUNTIME=OFF

Run the Interpreter

./obsl_runtime

Running without arguments starts the REPL. Pass a script path to execute a file, or use --lint to syntax-check a file and emit a JSON report:

./obsl_runtime --lint path/to/script.obsl

Only available when OBSL_BUILD_RUNTIME is ON (the default).


Quick Start

println "Hello, World!";

Variables are dynamically typed:

var score = 100;
var name = "Player";
var alive = true;
var nothing = null;

Language Basics

Variables

Variables are declared using the var keyword:

var number = 100;
var text = "hello";
var enabled = true;

Variables can change type:

var value = 10;
value = "Now a string";

Printing

ObSL provides print and println.

print (no newline)

print "Hello ";
print "World";

Output:

Hello World

println (with newline)

println "Hello";
println "World";

Output:

Hello
World

Operators

Arithmetic

var a = 10 + 5;
var b = 10 - 5;
var c = 5 * 2;
var d = 10 / 2;
var remainder = 10 % 3;

Comparison

a == b;
a != b;

a > b;
a >= b;

a < b;
a <= b;

Logical

true and false;
true or false;
!true;

// Alternative syntax
true && false;
true || false;

Assignment

a = 10;

a += 5;
a -= 2;
a *= 3;
a /= 2;

a++;
a--;

Bitwise

var bitAnd = 5 & 3;
var bitOr = 5 | 3;
var bitXor = 5 ^ 3;

var bitNot = ~0;

var left = 2 << 3;
var right = 16 >> 2;

Control Flow

If Statements

var score = 100;

if (score > 50) {
    println "Passed!";
} else {
    println "Failed!";
}

Switch

var value = 2;

switch (value) {
    case 1:
        println "One";
    case 2:
        println "Two";
    default:
        println "Unknown";
}

Type Checking

The is operator checks runtime type:

var value = 10;

if (value is number) {
    println "Number";
}

Supported Types

number
string
boolean / bool
null / nil
function / fn
array
object

Note: These are the type names accepted by is, which are distinct from the strings returned by the reflection function type_of() most notably, is uses function/fn where type_of() returns "callable". See the Standard Library Reference for type_of()'s exact return values.

Expressions

var result = 5 + 3;

if (result is number) {
    println "Math result";
}

Precedence

is binds tighter than equality checks:

(x == y) is boolean;

Null Checking

var value = null;

if (value is null) {
    println "Empty";
}

if (value is nil) {
    println "Also empty";
}

Loops

While

var i = 0;

while (i < 5) {
    println i;
    i++;
}

For

var total = 0;

for (var i = 0; i < 10; i++) {
    total += i;
}

Foreach

var numbers = [10, 20, 30];
var sum = 0;

foreach (var number in numbers) {
    sum += number;
}

Break

for (var i = 0; i < 10; i++) {
    if (i == 5) {
        break;
    }
}

Arrays

Arrays are dynamic and support negative indexing:

var arr = [10, 20, 30];

println arr[0];

arr[-1] = 99;

Negative Indexing

arr[-1] // last element
arr[-2] // second last element

Methods

println arr.len;

arr.push(100);

var item = arr.pop();

arr.clear();

Functions

Basic Functions

fn sayHello(name) {
    println "Hello " + name;
}

sayHello("Player");

Return Values

fn add(a, b) {
    return a + b;
}

var result = add(5, 10);

Default Parameters

fn damage(amount = 10) {
    return amount;
}

println damage();

Closures

fn createAdder(amount) {
    fn add(value) {
        return value + amount;
    }

    return add;
}

var addFive = createAdder(5);
println addFive(10);

Structs

struct Vector3 {
    x;
    y;
    z;
}

Default Values

struct Player {
    health = 100;
    name = "Unknown";
}

Example:

var player = Player();
println player.health;

Objects

var obj = Object();

obj.value = 42;
obj.printValue = printValue;

Modules

Modules are imported using the using statement. Each import creates a module object containing the module's exported variables, which can be accessed through that object.

Basic Import

using "assets/scripts/math.obsl";

var result = math.add(5, 10);

By default, the module name is derived from the filename without its extension. In this example, math.obsl becomes math.

Import Aliases

Use as to specify a custom name for an imported module:

using "assets/scripts/math.obsl" as Math;

var result = Math.add(5, 10);

The alias replaces the default filename-based name for that import. Here, the module is available as Math, not math.

Aliases are useful when multiple modules have the same filename or when a different name makes the code easier to understand.

Module Behavior

  • Path resolution: Module paths are resolved relative to the script root, which defaults to the current working directory.
  • Caching: Modules are cached by their canonical file path. Importing the same module under different aliases reuses the same cached module object.
  • Isolation: Module variables are accessed through the module object rather than being imported individually into the importing scope.
  • Circular imports: Circular imports are detected and reported as errors.
  • Custom loading: Embedding applications can provide a custom ModuleLoader to load modules from virtual filesystems or archives.

Error Handling

Try / Catch

try {
    var result = sqrt("invalid");
} catch (err) {
    println err;
}

Assertions

assert(player != null, "Player missing");

Throwing Errors

throw("Something failed!");

Built-in Library

Conversion

to_string(value);
to_fixed(value, decimals);
to_num(string);

Math

sqrt(x);
pow(a, b);
abs(x);
min(a, b);
max(a, b);
floor(x);
ceil(x);
round(x);
random();
clamp(value, min, max);

Trigonometry

sin(x);
cos(x);
tan(x);
atan2(y, x);

Helpers

pi();
rad(degrees);
deg(radians);
lerp(start, end, t);
map_value(value, inMin, inMax, outMin, outMax);

Strings

len(text);
to_upper(text);
to_lower(text);
trim(text);
contains(text, value);
starts_with(text, value);
substring(text, start, length);
replace(text, old, new);

Regex

regex_match(text, pattern);
regex_search(text, pattern);
regex_replace(text, pattern, replacement);
regex_find_all(text, pattern);

System Utilities

Files

file_exists(path);
read_file(path);
write_file(path, data);
get_file_ext(path);

Input

read();
readln();

Time

clock();
sleep_thread(seconds);

Environment

get_env(name);

Process

exit(code);

Reflection

Types

type_of(value);

Returns:

null
bool
number
string
array
object
callable

Functions

get_arity(function);

Objects

has_field(object, "name");
get_fields(object);

Garbage Collection

ObSL includes automatic garbage collection with cyclic reference handling.


Examples

See the Examples/ directory for additional runnable usage examples.


todo:

  • Proper Linter
  • LSP?

This is still very much a toy language :)


Resources & Acknowledgments

Learning Resources

ObSL's design and implementation were informed by:

Third-Party Libraries

Library License Purpose More
nlohmann/json v3.12.0 MIT JSON output for --lint mode docs/THIRD_PARTY_LICENSES.md

License

ObSL is licensed under the MIT License. See third-party dependency licenses in THIRD_PARTY_LICENSES.md.

About

A small, interpreted, thread-safe, dynamically-typed scripting language for game logic in the Obliberry Game Engine, designed to be easily extendable.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages