Hollicode v0.2.0

Hollicode is a minimalistic, writer-first programming language for interactive narratives.

The compiler outputs domain-specific bytecode that runs off a very slim set of instructions. Its output format is easy to parse and easy to run, and its nature as non-performance-intensive code (you'll be evaluating at most a couple dozen instructions at a time) means an interpreter can be implemented in only a couple hundred lines of a dynamic game engine language like Lua or GDScript.

Two warnings:

The project

I've been doing a thing lately where I implement mechanics from games I've enjoyed. I recently played Disco Elysium, and I've been thinking about how one might efficiently represent the systems-heavy, branching narratives in it without resorting to regular scripting. The idea of a language tailored around such a use case was intriguing, and I'd never written a compiler before, so I figured it was probably time to learn.

The name derives from a work-in-progress text-heavy RPG called Hollico. This programming language is its backbone.

Use case

Hollicode might be useful if....

Example

Execution goes from the top down and stops to get user input when it hits a wait command.

# Beginning
    The market's full of vendors, as markets tend to be. Against a wall nearby, an old shopkeeper is polishing a Red Delicious while humming to herself. In front of her is a cart of fruit.

    @option: Approach the shopkeeper
        You wander toward the shopkeeper.
        -> Talk to shopkeeper
    @option: Leave the market
        Nothing really grabs your interest.
        -> Leave
    @wait

# Talk to shopkeeper
    She looks up.
    "Hello there," she says. "Can I get you anything?"

    @option: Are those apples for sale?
        She hands one to you. "You know what? This one's on me."
    @option: No, just looking.
        She goes back to her polishing.
    @wait

# Leave
    The shopkeeper nods as you depart.

How does it work?

Hollicode comprises both a syntax and a bytecode instruction specification. The bytecode specification is designed to articulate text-based narratives of arbitrary complexity in a relative handful of instructions (check the docs page on the bytecode for more info). The syntax compiles to that bytecode.

However, where most bytecode-based programming languages provide a virtual machine and compiler in the same package so that you can embed the language directly, Hollicode's implementation has been engineered toward interpretation first. The compiler in this repository generates portable bytecode files intended to be interpreted in-game by a language-specific interpreter. The goal was (and is) to design a bytecode spec that could be easily interpreted in a dynamic language rather than a proper scripting language format. Porting Hollicode to a new platform requires only writing an interpreter, and in most higher-level languages, an interpreter only takes a couple hundred lines.

If you're trying to make a line of text display in your game only if a check succeeds, you might represent it like this in a theoretical narrative framework:

conversationParts["Critical part"] = function()
	say("You've reached a critical part in the conversation.")
	say("Shit's going down.")
	if checkCharisma() then
		say("And golly gee, you've brought your A-game.")
		goToConversationPart("A-game brought")
	else
		say("But you're feeling a little nauseated. Looking down, you notice a toothpaste stain on your necktie.")
		goToConversationPart("Alas, no A-game")
	end
end

Hollicode defines a set of bytecode instructions which can articulate this sort of thing and a super simple syntax that generates those instructions.

Your workflow when implementing a narrative in Hollicode will be to write in Hollicode's syntax (.hlc files), then compile to Hollicode's bytecode (writable in multiple formats like JSON or plain text), then load those compiled files in your game.

Building

The compiler is written in the (perenially lovely) Crystal programming language. Once you've installed Crystal, compiling Hollicode is as simple as:

crystal build --release src/hollicode.cr

You can then run the generated executable with --help to get a rundown on the compiler's features.

For a one-line build and install on Linuxy/Unixy systems:

sudo crystal build src/hollicode.cr --release -o /usr/local/bin/hollicode

What about....

Ink? Yarnspinner? Ren'Py? Twine? Any number of pet narrative languages? They're mostly great, but each one had one or more foundational issues (for me, at least--your mileage might vary):

If an alternative to Hollicode (the ones I've mentioned are all solid and loved by many) works for you, that's awesome. I'm not trying to start a fight or disparage good work. But if Hollicode's goals align more closely with yours, or you're just interested in the language, you might give Hollicode a shot.

There's a VS Code language package available, as well as a WIP Lua-based interpreter to try out. If you want to implement your own bytecode interpreter, look in docs/.

License

The code in the compiler is licensed under the Affero GPL version 3.0.

This license only applies to integrating with the compiler itself. Generated bytecode is your own. Unless your project uses the Hollicode compiler directly, you can treat your scripts and the generated bytecode as you would any other program generated by a compiler.

The officially-provided interpreters are licensed according to the license info in their repositories.