Hello all, in my quest to make my new upcoming grand strategy game with Godot I found I needed a scripting language, after a few days of research I came to the conclusion that every solution I could find wasn’t really what I wanted. So I spent the last week designing and developing my own scripting language that does everything I need, and then open sourced it here:
https://godotengine.org/asset-library/asset/3306
What is GDExpr?
GDExpr is a dynamically typed scripting language that compiles to a sequence of Godot expressions (Expression — Godot Engine (stable) documentation in English) and executes them.
It is written as a C++ GDExtension, in a single header file, in under 1000 lines of code that only uses godot-cpp headers, with no standard library usage. It is also written as a Godot add-on so can be used from either C++ (or any other GDExtension language) and GDScript.
GDExpr can be run as a JIT compiler to compile and execute code at runtime, so it allows for hot reloading and quick runtime testing. It can be used to statically compile GDExpr code to Godot expressions which can then be executed at runtime without incurring the compile time overhead.
GDExpr is a structured language and doesn’t even have any non-structural procedures (such as function declarations or goto statements) because you don’t actually need them. This means the control flow of any GDExpr program will always be a straight line from the top of the script to the bottom, it is impossible to jump to other sections of code that are not the next line to be executed, this is by design.
Unlike most sane languages that compile to byte code or machine code GDExpr instead compiles to Godot expressions. There are a lot of advantages to this approach, but there are also a few downsides.
The killer feature of Godot expressions that made me want to compile to them is their ability to call any GlobalScope functions or any function passed in on a object pointer.
All you have to do is write 1 line of code to bind the function in C++. In GDscript you don’t have to write any bindings at all, the functions bind automatically!
This makes your C++ code considerably simpler than most other scripting solutions would…
Compare this to creating function bindings from C->Lua: Exposing C functions to Lua - Sasank's Blog
Additionally Godot expressions natively support every Variant type and all operations on them out of the box…which means you can do pretty much anything with any type.
They also fully implement conditional logic and pretty much all the ways you can use it.
All of the features Godot expressions have don’t have to be implemented in GDExpr at all since godot expressions are able to handle them.
This makes the language implementation a lot more concise in addition to, having a simple API, and gdexpr syntax is also easy to use.
Another great feature of Godot expressions have is they will pretty much never cause crashes no matter how stupid the input you throw at it is, and sometimes it even gives sensible error messages! In my entire time writing this there hasn’t been any input I could throw at it that caused a crash.
Since the compiler also manages the runtime if the user makes syntax errors, the compiler will notify what expressions failed, the file it failed in, and the error message the Godot expression compiler emitted.
To see everything you can possibly do with GDExpr see the test.gdexpr
file in the demo scene of the github repository (and read the documentation…of course).
Why use GDExpr?
This is why I made GDExpr and what I plan to use it for.
GDExpr is not meant to be the main language you use to develop your game, it is designed to be an auxiliary scripting language.
Modern games are often developed using 2 different programming languages. A low level language to implement all the functionality and a much simpler scripting language to define the functionality. Often during development, tasks are delegated to the scripting language, and can be assigned to Content Designers rather than Programmers. It also makes the parts of your game implemented with the scripting language significantly easier to work since the scripts are generally much simpler than your actual game code. Tim Cain can explain it much better than I can: https://www.youtube.com/watch?v=ljnaL7N5qtw
In Godot the main scripting language is GDscript. I am writing my game entirely with GDExtension C++, using GDscript as the main scripting language, in this context feels awkward, due to the way Godot scripts must be tied to a node in the scene tree.
So what are my exact requirements for a scripting language?
In my mind the ideal scripting language would have all of these things:
-
Simplicity - Content designers should be able to fully understand all the language concepts with ease. It should also be fast to write because it is simple.
-
Dynamic Typing - Having to worry about statically typing variables isn’t something you should have to do when using a scripting language.
-
Ability to stay DRY (Don’t Repeat Yourself) - Simple config languages like JSON and YAML make it nearly impossible to not repeat yourself. An actual scripting language should have multiple ways to avoid code repetition, GDExpr does this with loops, C style macros, and C style includes.
-
Dynamic Configuration - All static config file formats are awful. This blog post explains exactly how I feel about this: Your configs suck? Try a real programming language. | beepb00p
-
Be Sandboxable - It should be possible to run the scripting language in a sandboxed environment, so only the functions you expose to the environment are possible to use. This enables you to use GDExpr as a scripting language for modding or other user generated content which should not crash the application and should not be able to call functions that might harm other users.
-
Easily bindable to C++ functions - If I purely wanted performance no doubt by far the best scripting solution would be LuaJIT…however binding Lua functions to C++ would make my game code that is already very complex even more complex, it also isn’t trivial to integrate in a cross platform way into the SCons build system, and it has 1 based indexing. It turns out that binding C++ functions to pretty much any programming language requires tons of boilerplate. I also looked into what other people were trying to bind to GDExtension and tried to use Julia and Angelscript to make bindings for. In contrast to every other solution I could find, GDExpr bindings are just 1 line of code when used with C++ and 0 lines of code when using with gdscript, making bindings trivial. Even binding functions to and parsing something extremely simple like JSON is more complex than GDExpr bindings since GDExpr is able to execute the functions directly on an object pointer.
-
Hot Reloading - You will have to rapidly iterate on ideas to make games, if the scripting language you use does not allow for quick testing of new configurations during runtime it is pretty much not even a scripting language. GDExpr works just like JIT compiler but also exposed an API that can statically compile GDexpr files. So it can hot reload changes in just a few microseconds or can be used to statically assign data.
-
Good Debugging - The scripting language should be simple enough to allow Content Developers to debug most problems without help.
-
Safe - It should be impossible for a scripting language to crash the application. GDExpr is designed to never crash, no matter what inputs you give it, the only way it can possibly crash is if you send inputs that your functions do not know how to handle.
-
Fast Execution - GDExpr should be fast. it’s difficult to benchmark how fast GDExpr is. There is more info on performance in the README.
GDExpr fulfills all of these requirements that I had in it’s current state, and it is still kind of raw, considering that I have only been working on it for a week so has tons of room for improvement and optimization. So if you have similar requirements for a scripting language in your project GDExpr could be useful for you, check it out here: GitHub - dementive/gdexpr: Scripting language for Godot 4
Also if you know anything about writing compilers/programming languages or just C++ in general, any contributions would be greatly appreciated