MiniLua

MiniLua is a lua interpreter with source value tracking.

Usage Example

minilua::Interpreter interpreter;

// parse the program
if (!interpreter.parse("x_coord = 10; force(x_coord, 25)")) {
    // parser failed
}

// run the program
minilua::EvalResult result = interpreter.evaluate();

// (optionally) apply a source change
if (result.source_change) {
    interpreter.apply_source_changes(
        result.source_change.value().collect_first_alternative());
}

// source code is now:
//
// xcoord = 25; force(x_coord, 25)

// re-run

Origin Tracking and Source Changes

This Lua interpreter tracks the Origin of all (or most) Values. The following Lua code

-- Lua Code
x = 10
y = 20
return x^2 + y^2

returns the value 500 and that value has the following origin:

BinaryOriginlhs: BinaryOriginlhs: LiteralOriginvalue: 10line: 1rhs: LiteralOriginvalue: 2line: 3rhs: BinaryOriginlhs: LiteralOriginvalue: 20line: 2rhs: LiteralOriginvalue: 2line: 3

This origin hierarchy allows the interpreter to generate SourceChanges if you want to force a value to a have a different value. I.e. here we force the value of the expression x^2 + y^2 (which is 500) to 400.

-- Lua Code
x = 10
y = 20
z_squared = x^2 + y^2
force(z_squared, 400)

This code would generate the following source changes:

SourceChangeAlternativeSourceChangeAlternativeSourceChange replace "10" on line 1 with "0"SourceChange replace the first "2" on line 3 with "-9223372036854775808"SourceChangeAlternativeSourceChange replace "20" on line 2 with "17.3205"SourceChange replace the second "2" on line 3 with "1.90397"

Here a SourceChangeAlternative means that exactly one of the children should be applied. In this case (because multiple SourceChangeAlternatives are nested) we could flatten the hierarchy. If we apply any of the source changes we will have forced the old value of z_squared (which was 500) to the new value 400.

Note that the second source change (replace the first "2" on line 3 with "-9223372036854775808") was generated due to floating point underflow and while it is correct and would yield the wanted result is probably not what you want to apply.

See also: Generating Custom SourceChanges when writing native functions.

Guide for Writing Native Functions

You can write your own C++ functions that are callable from Lua. These functions need to be compatible with one of the following signatures:

auto (minilua::CallContext) -> minilua::CallResult;
auto (minilua::CallContext) -> minilua::Value;
void (minilua::CallContext);

That means the return type has to be convertible to minilua::CallResult or minilua::Value or void (which equivalent to nil in lua) and the function needs exactly one arguments of type minilua::CallContext or const minilua::CallContext& (the latter is preferred because it avoids a copy/move and offer the same functionality).

You can use the minilua::CallContext to get access to the actual arguments and the global environment and to call other (Lua) functions.

Using Arguments

Examples for implementing a function that adds two values:

auto add_using_call_result(const minilua::CallContext& ctx) -> minilua::CallResult {
    auto arg1 = ctx.arguments().get(0);
    auto arg2 = ctx.arguments().get(1);

    return minilua::CallResult(minilua::Vallist(arg1 + arg2));
}

CallContext::arguments().get(n) returns the n-th argument (starting at 0). If that argument wasn't provided the function returns minilua::Nil. Note that you can't actually differentiate between the case where the user did not provide an argument and the user explicitly providing nil.

A more convenient way to write the same function is to directly return a Value:

auto add_using_value(const minilua::CallContext& ctx) -> minilua::Value {
    auto arg1 = ctx.arguments().get(0);
    auto arg2 = ctx.arguments().get(1);

    return arg1 + arg2;
}

Using the Global Environment

You can also access and modify the global environment. (Native functions do not have access to a local environment because they were not created in one.)

void add_to_global_env(const minilua::CallContext& ctx) {
    auto arg = ctx.arguments().get(0);
    auto value = ctx.environment().get("global_var");

    ctx.environment().add("global_var", value + arg);
}

Creating Values in Native Functions

You can create most Values (except Tables) directly through the corresponding constructor. If you want to create tables you have to call CallContext::make_table(). This will ensure that the table uses the same allocator as all other values in the interpreter. See Allocator.

auto create_a_table(const minilua::CallContext& ctx) -> minilua::Value {
    auto key = ctx.arguments().get(0);
    auto value = ctx.arguments().get(1);

    auto table = ctx.make_table();
    table[key] = value;

    return table;
}

Generating Custom SourceChanges

Working with Values

Working with SourceChanges

Allocator