My Big Ninkasi Presentation

So quite a while ago I started building a scripting language and, over the course of about a year, actually finished it (for some definitions of "finished"). The scripting language was named "Ninkasi", after the Sumerian beer goddess.

Years later, as part of a job interview, the company I was applying to wanted me to do a presentation to the programming team. The presentation could be whatever I wanted it to be, so I picked the one thing I'm more familiar with than anyone else, more complex than most stuff I've worked on, has well-defined goals, and is a project that I actually finished, and that was my scripting language.

I've gone ahead and transcribed the slides I made from that presentation here, for anyone interested in the inner workings or development of it.

It's a project I'm still damn proud of, even if there are a few things I'd do differently if I did it again, and I cover those things near the end of the presentation.

Original slides are available here, created on Aug 8th, 2020: Google Slides deck. (Going forward, any updates are going to happen on this article, so the slides may be outdated.)

The scripting language source itself is available here:

The preprocessor is available here:

1. Ninkasi

I made a scripting language and now you have to hear me talk about it.

2. Why on Earth would you do something like that?

I spent years on this so I better make this answer good.

3. Features I wanted

4. More features I wanted

5. Even more features I wanted

6. Features I wanted, last one

7. Okay I lied. This is the most important one

8. Other cool stuff

Wasn’t part of the original reasoning but I like it anyway!

9. Cool stuff

10. Cool stuff (cont.)

11. Cool stuff (cont.)

12. Example code

What the heck does this thing even look like?

13. C API Example

This is an extremely simple program that creates a VM, hooks up an output function, compiles a one-line hard-coded script, executes it, and cleans up. Error reporting is minimal.

#include "nkx.h"

#include <stdio.h>
#include <assert.h>

// "print" function callback.
void printFunc(struct NKVMFunctionCallbackData *data)
    nkuint32_t i;
    for(i = 0; i < data->argumentCount; i++) {
        printf("%s", nkxValueToString(data->vm, &data->arguments[i]));

int main(int argc, char *argv[])
    // Create the VM.
    struct NKVM *vm = nkxVmCreate();

    // Create a compiler so we can compile new code. (Loading binary
    // state snapshots doesn't need this.)
    struct NKCompilerState *compiler = nkxCompilerCreate(vm);

    // The most basic function we will need is "print". Otherwise it's
    // not possible to get anything out. (Well, it is. You just have
    // to have the hosting application explicitly pull data out of the
    // finished VM.)
        vm, compiler,
        "print",   // Used as as internal name for matching up during
                   // deserialization AND as a variable name so the
                   // script can call it.
        printFunc, // The function itself.
        nktrue,    // True to add this as a global variable (otherwise
                   // there is no way to call it right away).
        NK_INVALID_VALUE // Everything else is optional argument type
                         // and argument count checking.

    // Compile the script. (You would do this multiple times before
    // finalizing for multi-file scripts, or use a preprocessor to
    // make it all go in as a single string.)
    const char *scriptText = "print(\"foobar\\n\");\n";
        "internal" // Source file name (for error reporting).

    // Done adding source files to compile. Write the end and finish
    // setting up exported data.

    // TODO: Error check the compiler output for real (and display
    // error messages if needed).

    // Run the program. (There are other ways to trigger execution,
    // but this is the simplest.)

    // TODO: Error check execution.

    // Clean up and return success.
    return 0;

14. Ninkasi Code Example

Here's a simple example of Ninkasi script code. It generates a Mandelbrot pattern (using numbers instead of colors), and prints it to the console.

For this script to work, it would need a "print" function created, just like above in the C API example. ("print" is not a build-in function.)

// Mandelbrot generator demo for Ninkasi

for(var y = -1.0; y < 1.0; y = y + 0.05) {

    for(var x = -2.0; x < 1.0; x = x + 0.03) {

        var u = 0.0;
        var v = 0.0;
        var u2 = u * u;
        var v2 = v * v;
        var k;

        for(k = 1; k < 100 && u2 + v2 < 4.0; ++k) {
            v = 2.0 * u * v + y;
            u = u2 - v2 + x;
            u2 = u * u;
            v2 = v * v;

        if(k < 40) {
            print(k % 10);
        } else {


15. The language itself

16. Types

17. Syntax

18. Syntaxes

19. Syntax 3

20. Syntax: Resurrection


// This is the function that the coroutine will execute.
function functionToCall()
    // Yield control back to the parent context.

// Create the coroutine object.
var coroutineObject = coroutine(functionToCall);

// Run it.
print("1... ");
print("2... ");


1... Hello
2... there!

21. Error handling

What to do when everything explodes so you don’t HCF for real.

22. “Normal” runtime/compile errors

23. malloc() failure handling

24. malloc() failure is considered a "catastrophic error"

25. Multi-module compilation

Or how I learned to stop worrying and wrote a C89 preprocessor from scratch.

26. I wrote a C89 preprocessor from scratch.

27. … in C89.

28. Bytecode

There’s no assembler so if you want to go lower-level you get to do it the hard fun way.

29. 42 instructions

30. Memory

31. Serialized binary format

32. Difficult Fun bugs found during development

33. Real-mode DOS specific stuff

34. PowerPC (Linux) specific

35. Everywhere

36. Everywhere (cont.)

37. Lessons learned

38. Future Work

39. Faster hash tables

40. PUSHLITERAL_INT is like 1/4 to 1/2 of my generated instructions

41. Documentation’s not great

42. Add postfix increment/decrement

43. Remove the previously-mentioned anti-feature

44. “Closures” handled in a mediocre way

45. Variadic functions

46. Inline function compilation

47. Type queries

48. An actual standard library

That’s all!


Also, I'm counting this as my official first Gruedorf entry.

It's a bit coincidental, because I didn't realize it had been going until right before I decided to publish it (and also, hopefully, have it be the start of me blogging regularly again).

Posted: 2021-08-02

Tags: gruedorf, ninkasi