Skip to content

A scripting language for sharing code between Erlang and Godot.

License

Notifications You must be signed in to change notification settings

saltysystems/gdminus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gdminus

What and why?

gdminus is an implementation of GDScript for Erlang, allowing Erlang applications to share code with Godot.

gdminus is a component of a larger system, Overworld, that aims to be an Erlang-powered server framework for building scalable multiplayer games.

There may also be some generally useful snippets in this codebase for anyone wanting to build a whitespace-significant language that runs on the BEAM.

Scope and Plans

The intent is to implement a strict subset of GDScript that facilitates communication between Erlang-based game servers and Godot-based clients.

gdminus will probably not implement keywords supporting coroutines, signals, or networking RPC. These include yield, signal, remote, master, puppet, and so on.

gdminus does not currently support classes, but may in the future if they are not too onerous to implement.

Current State

A working lexer, parser and tree-walking interpreter have been developed that support a generous subset of the language including:

  • Arithmetic expressions and Boolean comparison operators.
  • If/Else statements
  • While/For statements with break and continue
  • Match statements
  • Functions, including a number of built-in functions. See e.g. examples/fib.gd or examples/math.gd
  • Dictionaries and arrays

The lexer has been implemented using leex and the parser is implemented via yecc. The interpreter takes a great deal of inspiration from Luerl as well as Crafting Interpreters.

Performance

gdminus is easily 10x slower than Godot for many applications. In fact, gdminus will probably never be as fast as Godot's built-in GDScript virtual machine. This is due to inherent slowness in using a treewalking interpreter, overheads incurred by implementing a procedural and mutable language in the BEAM, and general programmer inefficiency :)

Calculating the first 25 numbers in the Fibonacci sequence using the recursive implementation yields:

Implementation Time
Godot 88ms
gdminus (OTP/24 with JIT) 673ms
gdminus (OTP/22) 713ms

(on an i7-7600U with absolutely no performance optimizations)

Known Caveats

There have been no efforts to date to ensure gdminus rigorously follows GDScript semantics, nor have any of the re-implementations of various built-in functions been verified for correctness. Caution is advised around floating point math especially.

The implementation is decidedly uncouth for Erlang code for any number of reasons. The author is rather unhappy with abusing the process dictionary to hold state in the lexer and interpreter.

Build / Test

    $ rebar3 shell
    1> gdminus_test:regenerate().
    2> c(gdminus_scan).
    3> c(gdminus_parse).
    4> gdminus_int:file("examples/minimal.gdm")

Examples

Fibonacci

Calculate the first 25 Fibonacci numbers:

func fib(n):
    if n < 2:
        return 1
    else:
        return fib(n-1) + fib(n-2)

func time():
    return OS.get_ticks_msec()

var start = time()
print(fib(25))
var end = time()
print("Time: " + str(end - start) + "ms")

gdminus will return a 3-tuple to the shell in the format {Stdout,Stderr,FinalState}. Standard out and standard error are represented as lists with each new line representing a list item. The final state contains the user-defined function table plus any variables defined and so on.

1> gdminus_int:file("examples/fib.gdm").
{[121393,"Time: 1395ms"],
 [],
 {state,0,0,
  #{0 =>
     {env,
      #{"fib" =>
         {[{name,1,"n"}],
          [{'if',
            {'<',{name,2,"n"},{number,2,2}},
            [{return,{number,3,1}}]},
           {else,
            [{return,
              {'+',
               {func_call,{name,5,"fib"},[{'-',{name,...},{...}}]},
               {func_call,{name,5,"fib"},[{'-',{...},...}]}}}]}]},
        "time" =>
         {[],
          [{return,
            {func_call,
             {{name,8,"OS"},{string,8,"get_ticks_msec"}},
             []}}]}},
      #{"end" => 1629431927532,"start" => 1629431926137}}},
  #{},[]}}

Custom functions

gdminus allows an application to add custom functions to the function table. Here we add an application function, erf(float) (representing the Error function), callable from gdminus:

1>  gdminus_int:init().
ok
2> F1 = fun([X]) -> math:erf(X) end.
#Fun<erl_eval.44.40011524>
3> gdminus_int:insert_function("erf", F1).
ok
4> gdminus_int:do("print(erf(0.42))").
{[0.4474676184260253],
 [],
  {state,0,0,#{},#{},
          #{"erf" => #Fun<erl_eval.44.40011524>},
                  []}}

gdminus functions created this way must return a value.

gdminus will first evaluate locally defined functions, application functions (such as erf/1 in this example), and finally builtin functions (a subset of canonical gdscript functions).

About

A scripting language for sharing code between Erlang and Godot.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages