April 29, 2015

Scripting Languages, Bytecode, and Feature Creep (Project Postmortem)

At the beginning of this semester I embarked on a small project to create a ZZT-OOP style language for my Console Programming class.  For several reasons, this did not work as intended, but the experience was still worthwhile.

My original objective was to create a simple scripting language, complete with syntax, parser, and runtime, to enable designers to more easily script behavior.  I was inspired by ZZT-OOP, a language included with the influential 90's ZZT game and world editor.

This goal got away from me as I delved further into language design.  I started out trying to define the syntax, but found that it was easier to write the bytecode instructions first.  Each instruction contains two parts - an opcode and an optional argument.  A program is just an ordered list of these instructions (with optional labels) combined with a program stack, a data stack, and a context stack, to maintain which variable table is being referenced.  Most opcodes operate on the data stack, pushing or popping values.  Others push data from the variable table onto the data stack or push the top value from the data stack into a spot in the variable table.  Still others branch or jump to various labels within the program, allowing for the creation of loops and conditionals.

This created a really cool demo, but led to the creation of something that would require more work to interpret compared to the single-instruction, command-line style of ZZT-OOP.  I began looking more closely into Lua, borrowing its "table" concept for variables.  Unfortunately, this led to an attempt at parsing Lua-style syntax, which, if you've ever compared ZZT-OOP to Lua, it's night and day.  Lua, in comparison, is significantly more flexible, which makes parsing it much more of a challenge.  These difficulties served to slow my progress, and with the constant need for progress in my Production course, my work ground to a halt.

Luckily, this attempt was not for naught.  I definitely understand more about how languages work behind the scenes and understand the bytecode pattern much more completely.  I also learned a lot about the limitations of C# in creating a scripting language.  Most other implementations are in C or C++, which allow for unions and treating strings as char pointers.  Comparatively, C# requires that you hack unions with FieldOffset, and strings are treated as managed copy-on-write objects, making it impossible to embed them within a C# struct "union".  I also received another lesson in scope management, and should probably not get distracted by shiny popular languages in the future if I return to this endeavor.

So, the most important takeaways from this project: bytecode is pretty easy, language specifications are more difficult, and translating between the two is tricky.  Also, designers don't really enjoy writing code, so a visual scripting tool is not only easier to use, but easier to interpret into bytecode.  If I return to this, I'll likely make some kind of WinForms tool instead of a full-blown written and parsed language.  It's a bit more effort from a tools perspective, but if my goal really is to empower designers, then this is a much better alternative.

No comments: