Skip to content

138paulmiller/aug

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AUG

Augment your systems

Easily embeddable and highly extensible scripting engine.

Example

import std

func quicksort(arr, low, high){

    if low < high {

        var pivot_idx = floor((low + high) / 2)
        swap(arr[pivot_idx], arr[high])

        var pivot = arr[high]
        var i = low - 1

        for j in low:high{
            if arr[j] < pivot {
                i += 1
                swap(arr[i], arr[j])
            }
        }

        swap(arr[i + 1], arr[high])
        pivot_idx =  i + 1

        quicksort(arr, low, pivot_idx - 1)
        quicksort(arr, pivot_idx + 1, high)
    }
}

var arr = [10, 3, 8, 4, 2]
var result = [ 2, 3, 4, 8, 10 ]
quicksort(arr, 0, length(arr) - 1)
expect(arr = result, "quicksort(", arr, ")")

NOTE: The above example relies on user-defined function calls from the testing library. See here

Features

The Aug programming language supports:

  • Dynamic typing, built-in string, array, hashmap, and first-class function data types.
  • Simple code structure and control flow via if, for, while
  • Simple bidirectional interoperability that facilitates scripts communication with native code.

Language Embedding

The primary focus of this language was to create a simple programming language implementation that can be easily embedded into existing applications. To support this, aug.h provides an API that operates on the engine's virtual machine. This virtual machine contains some global script state, like registered extensions. The API supports a few different use cases, primarily executing and loading compiled scripts.

Executing scripts

When executing a script, the script source will be compiled to bytecode. Then the virtual machine will boot into this bytecode, execute the instructions, return, and reset state. This is useful if users wish to fire-off one-shot script tasks or jobs.

Load scripts

When compiling a script, the source code will be compiled to bytecode. The virtual machine will boot into this bytecode, execute the instructions, and return. The virtual machine stack may or may not retain state, depending on the api call. For instance, aug_execute calls will reset the stack state, where as aug_call will retain the stack state after the operation. See [AUG FFI](#AUG FFI) Or in other words, it will execute the script. However, the script will retain the global state of its variables, this will allows users to modify the state of the script by calling on it's functions.

Header Usage

Include Source

aug.h implementation is insipired by the stb style libraries.

At least one source file must contain the implementation details for this project. Meaning at least on c/cpp files must contain the following

#define AUG_IMPLEMENTATION
#include "aug.h"

Virtual Machine

  1. Initialize the Aug Virtual Machine (VM). To do this, first startup the VM
aug_vm* vm = aug_startup(NULL);

An optional error messaging handler can be provided as such

void on_error(const char* msg)
{
    fprintf(stderr, "[ERROR]%s\t\n", msg);
}
aug_vm* vm = aug_startup(on_error);
  1. When finished using, cleanup the memory and state
aug_shutdown(vm);

Executing scripts

  1. To execute an aug script, simply call aug_execute.

Here is an example of the minimal use case. Boot a VM instance, compiles, loads the script into the VM, executes, then shutsdown.

aug_vm* vm = aug_startup(NULL);
aug_execute(vm, "path/to/file");
aug_shutdown(vm);

Interoperability

C FFI

The C foreign function interface for this project makes use of function extensions, which are essentially registered function pointers. These extension functions are in place to extend the scripting capabilities by providing interoperability between the aug scripts and your native application. To register these extensions, users must first define and implement a thin wrapper function in C. These external functions expect a function signature similar to the C main function signature (see code below). To bind your local function to a script function you must use the *aug_register function.

aug_value function(int argc, aug_value* args) { ... }
aug_register(vm, "function", external_function);

argc - the number of values passed into the function args - array of argument values passed into the function return - value returned back to the script calling context

AUG FFI

Inverse to the C FFI, this project provides a mechanism for calling script functions from C. To do this, the scripts must first be loaded into the VM. This will execute the global statements of the script and retain the state of the into the script. Then, at a later point in time, users can call script define functions, that may or may not modify the global state of the script.

  1. Load the script

Loading scripts from external files can be done using the aug_load function. This function will return an instance of the script. Scripts retain their global state, and will keep references to any globals values until they are unloaded using aug_unload

Note: This example sssumes the VM has already been booted via aug_startup.

aug_script* script = aug_load(vm, "path/to/file.aug");
...
aug_unload(script);
  1. Call the script functions

Note: This example assumes the VM has already been booted via aug_startup. Note: This example assumes that the file.aug contains function defintions for Setup() and Update(delta)

Once you have a loaded script instance, you can call functions defined in the script using both aug_call and aug_call_args.

aug_script* script = aug_load(vm. "path/to/file.aug");

aug_call(vm, script, "Setup");

aug_value args[] = { aug_create_float(100) };
aug_call_args(vm, script, "Update", 1, &args[0]);

aug_unload(script);

Interop Example

A complete example for executing the first 30 integers of the fibonacci series This example makes use of most of the libraries FFI features, allowing users to define and call functions in both languages.

fib.aug

func fibonacci(n) {
    var a = 0
    var b = 1
    var sum = 0
    for i in 0:n {
        a = b
        b = sum 
        sum = a + b
    }
    return sum
}

main.c

aug_vm* vm = aug_startup(NULL); aug_script* script = aug_load(vm, "fib.aug");

aug_value args[] = { aug_create_int(30) };
aug_value ret = aug_call_args(vm, script, "fib", 1, args);

printf("fib(30)=%d", ret.i);

aug_unload(script);
aug_shutdown(vm);

Note: If these scripts are intended to have a long lifecycle, keeping a handle on the script and VM will allow users to contiuously update and execute the script. Something like this:

aug_vm* vm = aug_startup(NULL);

aug_script* script = aug_load(vm, "entity.aug");
bool running = true;
while(running)
{
    aug_value args[] = { aug_create_int(time(NULL)) };
    aug_call_args(vm, script, "update", 1, args);
	...
}
aug_unload(script);
aug_shutdown(vm);

Libraries

There is a special keyword, import that allows users to load precompiled libraries into the aug runtime. In the script calling import example; will search for a dynamic library name example. As of now, it will look for the library in the applications working directory.

To create the example lib, users must include the aug header and define a function that registers all the lib extensions. AUG expect the library function entry point to be named aug_register_lib. Below is standard example of a library that will be loaded by the use statement.

#include "aug.h"
AUG_LIBCALL void aug_register_lib(aug_vm* vm)
{
	aug_register(vm, "lib_function", lib_function);
	...
}

For example, if this is compiled to a dynamic library named example.so on linux, or example.dll on windows, and this is placed under the working directory of the executable, the it can be imported via

import example;

Compiling libraries is OS specific, for an example library used by the test utility see here

Demo

  • For examples of use cases see the test cases here
  • For a working demo of misc demos, see here

Syntax

  • NOTE: As of now, optional semicolons are enabled by default. This feature can be toggled on-off via the AUG_ALLOW_NO_SEMICOLON macro.
  • NOTE: As of now, single line blocks are enabled by default. This feature can be toggled on-off via the AUG_ALLOW_SINGLE_STMT_BLOCK macro.
NAME    : \w+[\w\d_]+

BINOP   : + - * / < > ^ % == ~=  
         += -= *= /= <= >= ^= %= 
         and or

NUMBER  : [-+]?\d* 
        | [-+]?\d*.\d*
        | 0b[01]+ 
        | 0x[0-9a-fA-F]+

STRING  : ".*"

CHAR    : '.'

BOOL    : true 
        | false

block   : { stmts }

params  : NAME  params
        | , NAME params
        | NULL

stmt    : expr ;
        | VAR NAME = expr ;
        | WHILE expr { stmts }
        | FUNC NAME ( params ) block
        | IF expr block 
        | IF expr block ELSE block 
        | IF expr block ELSE cond

stmts   : stmt stmts
        | NULL

expr    : value 
        | expr BINOP expr 
        | UNOP expr

func_call : NAME ( args )

args    : expr args
        | , expr args
        | NULL

value   : NAME 
        | func_call 
        | NUMBER 
        | STRING 
        | CHAR 
        | BOOL
        | ( expr )
        | [ args ]