/ppga

A scripting language that transpiles to LUA

Primary LanguageC++

PPGA Script

PPGA Script is a scripting language that transpiles to Lua, original designed to be used in https://github.com/jprochazk/anikibot. It provides a more familiar C-style syntax and syntactic sugar, designed to reduce the amount of boilerplate when writing scripting commands in lua.

See tour.ppga for a quick tour.

Binary Installation

  • You may clone the repo and build from source, then use the scripts ppga.sh and ppga.bat.

OR

  • You may install the binary with cargo install from this repo:

    $ cargo install --git https://github.com/OptimalStrategy/ppga/ --features=build-binary

Library

There's two implementations of the transpiler, in Rust and C++.

Syntax Highlighting

There's a syntax highlighting plugin for vscode. It can be installed from the vscode-ppga directory.

Language Reference

Feature Description Example Generated Lua
Literals None.
1;
3.141592; 
true; 
false; 
nil; 
"a string";
1
3.141592
true
false
nil
"a string"
Array and Dict literals None.
// Arrays use python-style syntax 
// and are initialized from 0.
let arr = [1, 2, 3];

// Dicts are similar to Lua but 
// don't require the `[]`
let dict = {1 = 2, 3 = 4};

// Indexing uses the [] syntax: 
let empty = {};
empty["string"] = "hello";
local arr = {[0] = 1, [1] = 2, [2] = 3}

local dict = {
    [1] = 2,
    [3] = 4
}

local empty = {}
empty["string"] = "hello"
F-strings An interpolated string expression that compiles to concatenation of string literals and tostring() calls. A backslash can be used to escape formatter brackets: \{}.
print(f"{a} + {b} = {a + b}");
print(f"\{escaped}");
print(tostring(a) .. " + " .. tostring(b) 
    .. " = " .. tostring(a + b))
print("{escaped}")
Arithmetic Expressions None.
print(1 + 2 * 3 / 4 ** 5 % 10);
print(1 + 2 * 3 / 4 ^ 5 % 10)
Integer Division Integer division operator.
print(1 \ 2);
print(1 // 2)
Comparison and Equality operators None.
print(3 < 4, 5 <= 6, 8 > 7, 9 >= 8, 10 != 11, 
    7 == 7);
print(3 < 4, 5 <= 6, 8 > 7, 9 >= 8, 10 ~= 11, 
    7 == 7)
Logic operators None.
print(true and false or true);
print(true and false or true)
Concatenation Operator This operator is the same as in lua. Reusing `+` for concatenation is not possible without rolling out a type system.
print("a" .. "b");
print("a" .. "b")
Default Operator This operator is similar to `??` in C#. If `a` is not `nil`, its value will be returned, otherwise, the `b` value will be returned. This feature requires the PPGA internals included.
print(a ?? b);
print(__PPGA_INTERNAL_DEFAULT(a, b))
Variable Declarations Let bindings correspond to `local` lua variables, while `global` ones transpile to variables without a binding keyword. A `global` variable must be initialized at declaration.
let a; 
global b = 4;
local a
b = 4
Function Declarations All functions are `local` by default. The `global` keyword may be used to make them global. The "fat arrow" syntax can be used if the function's body is a single expression.
global fn f() {}
fn g() {}
fn h(x) => x * x
function f()
end

local function g()
end

local function h(x)
    return (x * x)
end
Lambda Expressions Lambdas use the same syntax as named functions, except that they don't need an identifier.
print(fn(y, f) {});
print(fn(x) => x * x);
print(function (y, f)
    end)
print(function (x)
        return (x * x)
    end)
Rest Arguments / Variadics Rest arguments use the `@` symbol and transpile to `...`.
fn f(a, @) {
    print(a, @);
}
local function f(a, ...)
    print(a, ...)
end
Ellipsis / Unpacking Strips the parentheses and unpacks the given expression with `table.unpack`.
fn f(x) {
    fn packed() {
        return x, 5;
    }

    if not x {
        // returns the result of packed 
        // as a single value:
        // => return (packed())
        return packed();  
    }

    // unpacks the result of packed() as two values:
    // => return unpack({packed()})
    return ...packed();  
}
local function f(x)
    local function packed()
        return (y), (5)
    end

    if not(x) then
        return (packed())
    end

    return __PPGA_INTERNAL_UNPACK(packed())
end
For Loop (ranges) For-range loops transpile to Lua range loops
// From 0 to 3
for i in range(3) {
    print(i);
}

// From 2 to 4
for i in range(2, 4) { 
    print(i);
}

// From 0 to 10 with step = 2
for i in range(0, 10, 2) { 
    print(i);
}
for i = 0, 3, 1 do
    print(i)
end

for i = 2, 4, 1 do
    print(i)
end

for i = 0, 10, 2 do
    print(i)
end
For Loop (containers) For-in loops transpile to `pairs` or `ipairs` depending on the keyword used.
let container = [1, 2, 3];
container["string"] = "hello";

// Table iteration, uses pairs
for key, value in container {
    print(key, value);
}

// Array iteration, uses ipairs
fori idx, value in container {
    print(idx, value);
}
local container = {[0] = 1, [1] = 2, [2] = 3}
container["string"] = "hello"

for key, value in pairs(container) do
    print(key, value)
end

for idx, value in ipairs(container) do
    print(idx, value)
end
Error Propagation with `?` and `err` Blocks None.
fn may_fail(fail) {
    if fail {
        return nil, "error";
    }
    return "success", nil;
}

fn main() {
    // The ? operator simplifies Go-style 
    // error handling. By default, this 
    // will make the whole program 
    // crash if an error is encountered.
    let ok = may_fail(false)?;
    print(f"First result: {ok}");

    // Sometimes it is desirable to log or 
    // try to recover from the error.
    // An err block may be used for this purpose:
    let ok = may_fail(true) err {
        print(f"An error has occurred: {err}");
        return ...recovery();
    }?;
    return ok;
}
local function may_fail(fail)
    if fail then
        return (nil), ("error")
    end
    return ("success"), (nil)
end

local function main()
    local ok = nil
    do
        local _ok_L10S283, _err_L10S283 = 
        __PPGA_INTERNAL_HANDLE_ERR(
            __PPGA_INTERNAL_DFLT_ERR_CB, 
            may_fail(false)
        )
        if _err_L10S283 ~= nil then
            return (nil), (_err_L10S283)
        end
        ok = _ok_L10S283
    end
    print("First result: " .. tostring(ok))

    local ok = nil
    do
        local _ok_L18S562, _err_L18S562 = 
        __PPGA_INTERNAL_HANDLE_ERR(
            function (err) 
                print("An error has occurred: " 
                    .. tostring(err))
                return (unpack(recovery()))
            end,
            may_fail(true)
        )
        if _err_L18S562 ~= nil then
            return (nil), (_err_L18S562)
        end
        ok = _ok_L18S562
    end
    
    return (ok)
end

a hack to widen the column