Gameplay scripting
For my engine, I want to use a scripting language. The primary purpose for employing a scripting language is to simplify the development of gameplay code and leverage the power of multi-core systems. I’ve been lately a bit more serious about it (and yes, the packages are still in development, good stuff takes time) and did some more research. Read on for design goals, parser generators and general rambling on programming languages. By the way, this is the 100th entry in my blog :) Hooray!
Design goals
The scripting language should first and foremost allow designers and artists to write gameplay code, not more, not less. It does not have to be a fully features scripting language like Python. The focus is on event-driven, mostly parallel gameplay code. This brings several requirements with it:
- Event-driven code has to be supported natively. Basically, there has to be some keyword for events as everything in niven is going to be event-driven.
- Parallel execution. The resulting code should run well on a parallel ScriptVM.
Of course, if most of your code is event-driven, parallel execution comes for free as long as your objects are independent of each other. The most simple solution is to stack up all events for an object and then process the objects in parallel. This is completely transparent to the user and does not even require special language support. This means also that event raising will be asynchronous, but that seems rather logical to me. More design goals will probably emerge as soon as I get a prototype working, but these are the primary ones for now.
Syntax
A word on syntax, what kind of syntax do you prefer given the following choices?
- C with
function
function int a (int b) { ... }
- C, trailing types
function a (b: int) : int { ... }
or a slight variation, with a more math-inspired syntax:
function (a:int, b:int) -> int
{ ... }
- A really verbose variant:
function name:a params:{a:int, b:int} return:int { ... }
More verbose, but very friendly to annotations. Imagine if you could add other “attributes” to a function like
function name:qsort implements:sort-algorithm complexity:nlogn ...
and get it via sort-algorithm.get("complexity","nlogn")
?
A note on the last one: I have some idea of something I call a “Rich metadata engine” which would allow adding attributes to each parameter, variable or function. For example, const and reference would become attributes and passed over to the compiler. This would allow compilers to support new attributes (say, checks like [>10]
) which could lead to better optimization and better compile-time checks. Image the example
function a (
q:array [const,ref],
i:int [const,=size(q)])
Parser generators
To make all this stuff happen, I have to write a powerful script VM and a good compiler, and both tasks are not simple.
Pyl
For the compiler, I was playing around with the idea to write it in Python, but unfortunately parser generators for Python are not very powerful (the best I’ve found was Pyl, a yacc port to Python) and transforming ASTs in Python is painful because Python does not know call-by-reference. Anyway, for quick prototyping it’s really good, I managed to get a compiler with constant-folding up & running in 2 hours for a simple test “language” (which understood assignment to variables and basic maths).
Boost.Spirit
Next try was Boost.Spirit, truly impressive, took me also around 2 hours to get a sample running (this time not written from scratch though) and write a simple compiler. Spirit is probably better suited for simpler parsing tasks, although I’ve seen some impressive parsers written with it. I wouldn’t do it because although there is something beautiful about embedding EBNF in C++ I still see the advantage of writing real EBNF and transforming to ugly C++ ;) which leads me to the last tool I’ve tried, ANTLR.
ANTLR
It comes with a truly amazing IDE (yeah, you read right. It really has an IDE for writing parsers), it supports LL(*) grammars (no left-recursion though, but the IDE has a built-in function to refactor left-recursive rules) and it generates ASTs if you want it to. Unfortunately, the current version is under heavy development and not finished yet, meaning I didn’t have the chance to play with the C++ AST yet. Anyway, I’ve been able to implement a rather large grammar already in no time, and I’ll stick with ANTLR until something better pops up which is kinda unlikely.