Process of writing a Z-machine interpreter

Hi all!

I’ve been wanting to write a Z-machine interpreter and wanted to get the community’s advice on the best process to follow.

I’m a programmer by trade but haven’t written any sort of interpreter or emulator before. I have a conceptual understanding of how CPUs work and interact with memory and I/O, but no hands-on experience implementing a virtual one.

So far I’ve been following two documents:

  1. The official spec which I find quite terse and disjointed (but that’s probably due to my lack of experience in this area). I suppose it’s a good reference when you have specific questions, but as someone trying to understand the big picture it feels overwhelming.
  2. The Z-machine, And How To Emulate It – a PDF much heavier in prose but dives into the high level concepts of the Z-machine. This document was published in 1996 whereas the spec was updated in 2014, so I’m unsure if I’d be missing anything critical by following this guide.

My current strategy has been to build out specific functions of the interpreter. For instance, I’ve implemented functions to decode a Z-string into printable chars given an arbitrary memory address, and I was going to move onto user input encoding next. While I’m happy with this progress, it feels like I’m not seeing the forest for the trees.

I wonder if it’s more prudent to start by writing a skeleton; say the main loop to fetch and decode opcodes from memory, and then implement the functions necessary to process each opcode as I encounter them?

Thanks in advance!

3 Likes

Welcome to the forum!

Yep, that’s definitely a valid strategy, I’ve done it before. I suggest using the Praxix unit test because then you’ll know you’re covering almost all the opcodes.

The Z-Machine isn’t too complicated, and most complex interpreters probably spend their complexity on optimisations. If you’re just intending to write a basic interpreter, then it should be a fun little project.

1 Like

I’m currently writing interpreters as a learning experience, and what I like when starting with the main loop and implementing encountered opcodes is that it’s a lot more motivating.

Take a game you like (or maybe better, a game you wrote) and implement each encountered opcode until you can go through the game from start to finish. It’s really pleasant to see the game being played in your own interpreter.

And after you can use the unit test file to implement the missing opcodes and test them.

In any case, that’s how I do it, but I suppose any way that works for you is good.

1 Like

You might also like checking out Internal Secrets of Infocom Games.

1 Like

The guide should be fine. The changes since 1996 are polishing rough edges, plus a few optional features which you don’t need to worry about at the start.

You could start with

[ Main; print "Hello.^"; ];

for example. :)

2 Likes

Thanks all!

I’m going to start off with the main loop, and test with a combination of Praxix, Jigsaw (a game I really enjoy) and [ Main; print "Hello.^"; ];. I’ve never written or compiled a Z-machine game before so I’m keen to try this.

That’s great to know! I find that guide much more approachable than the reference, so I might stick with it until I need more specifics.

Nice! Some bedtime reading for tonight.

Yeah, I’m looking forward to this satisfaction the most. I haven’t played much IF, but the games I’ve tried have captivated me for days on end. If I look at it on purely a technical level, it’s astounding that we can bring a measly couple hundred kilobytes of text to life the way we do.

I’ll try and keep this in mind and not overcomplicate things as I tend to do!

2 Likes

Oh, just remembered an implementation tip I like to give out now: the standard doesn’t specify how the call stack must be implemented, and there’s lot of ways you could structure it. I recommend though giving serious thought to using the Quetzal savefile’s stack format as your interpreter’s native call stack format. It’s maybe slightly quirky and not how you’d do it yourself, but if your native format is the same as Quetzal then it makes saving and restoring almost trivial.

There are also some notes and errata on Github that haven’t been incorporated into the spec yet: Issues · DFillmore/Z-Machine-Standard · GitHub

2 Likes

Thanks for that! My naive approach to saving and loading was going to be serialising the state of the memory, call stack, and PC, but following the Quetzal spec seems like a much better idea.