Hello! I have been learning more about Inform 6 and the Z-machine, and to cement my understanding I thought I’d try doing a couple of programming puzzles with Inform 6. I wrote up my experiences in an article, which is now in a “final draft” sort of stage.
If you
would like to review the article and/or
have any other comments and/or
are an expert on Inform 6 or the Z-machine and can point out everywhere I’m wrong
before I publish it for a wider audience, that would be greatly appreciated.
As far as I know, dynamic memory allocation was first used on the Z-machine in Inform 7; its RAM is limited enough that previously making a heap was more trouble than it was worth.
The Z-machine itself isn’t object-oriented; it has objects, with properties and attributes, but message-passing (and anonymous routines stored in properties) is an Inform 6 thing built on top of it.
As far as I know, dynamic memory allocation was first used on the Z-machine in Inform 7
I think this is responding to footnote 4:
There are Infocom games that relied on dynamic allocation and they had to twist the Z-machine in awkward ways to achieve that […]
I suspect that’s just a typo for “Inform games”, as Infocom never did this. Lists and Lists must be the first example – and its dynamic allocation model is terrible, to be clear.
I don’t know if there’s a second example before Inform 7 came along.
“First off, it is really low level. From what I understand, not even the people at Infocom wrote raw zil. Instead, they used Lisp macros that generated zil.”
the old infocom source code is very much ZIL. they did use macros but they were in MDL (the language ZIL is derived from) not lisp (the language MDL is derived from).
Ah, that was a misunderstanding on my part. I had a vague memory around how object action routines worked in ZIL and extrapolated into the Z-machine being object oriented, but my memory was wrong.
Yup, thanks for spotting the typo. The words are close enough that I’m worried I may have made the same typo in many other places!
Maybe I could make that more clear. In the article I’m using “Lisp” to mean “the entire Lisp family”, including things like Common Lisp, Lisp Machine Lisp, LISP 1.5, Clojure, and indeed MDL.
From what I understand, not even the people at Infocom wrote raw zil. Instead, they used Lisp macros that generated zil.
They used macros for many things, but the macros are part of ZIL, because ZIL is essentially an extension of MDL: being able to define a domain-specific language to suit the needs of your game is one of the selling points.
What they didn’t write by hand is code for ZAP, the Z-machine assembler.
Games written in zil don’t really have standardised parsers and world models. If we wrote a game in zil we’d have to copy the parser and world model from some other game to serve as our game engine, and then we’d get whatever quirks suited that game along the ride.
This was accurate for most of Infocom’s existence (pre-V6), but today it’s no longer the case. Just like Inform 6, ZILF ships with a standardized parser and world model. You can copy it into your game and tweak it if you want, but you likely won’t need to.
Inform 6 is another language that compiles to Z-machine bytecode. It is very similar to zil, which is remarkable because it was created before the zil source code for the Infocom classics was released
I wouldn’t say Inform 6 is similar to ZIL, except in the sense that they’re both similar to Z-machine assembly.
They’re both low-level languages with a lot of constructs that map directly to Z-machine opcodes, but beyond that, the syntax, philosophy, and standard library are very different. Inform programs are built out of imperative statements; ZIL programs are built out of expressions, which each produce a value. Inform’s parser matches commands against a precise grammar; ZIL’s parser picks words out based on their part of speech and fits them into a standard sentence template. Inform supports an object-oriented paradigm, with methods and inheritance; ZIL doesn’t.
@aread doesn’t require you to give the second argument (“parse”). If you don’t need the Z-machine interpreter to split up the input into words and look them up in the dictionary, you can just omit that argument.
(This is Z-machine version 5. In earlier versions, the opcode is called sread and the second argument is mandatory.)
In Inform 6, you can trust all local variables that haven’t received values through the procedure call to get the value 0, regardless of Z-machine version, or even if compiling to Glulx.
(On the Z-machine level: In version 5, every routine begins with a single byte saying how many local variables the routine has, and the interpreter is responsible for setting them up and giving them the value 0, unless they get a value in the procedure call. In version 1-4, each routine has this initial byte, plus that many words giving the default values for all variables. AFAIK, there is no way in Inform 6 to set these default values, i.e. they’re always zero.)
Do I understand it correctly that MDL allowed implementers to define custom macros, and then some of these macros were useful and got effectively converted into language constructs in ZIL? Or does ZIL allow defining custom macros?
Thank you! I will correct that misunderstanding.
I think that’s precisely the way in which they look similar to me as a relative outsider.
Ah, maybe that was why I thought it was mandatory. You’re right – I can omit it and it works just fine. Thanks also for the details on argument initialisation.
That’s an opportunity for an Inform optimization – if a routine starts with “local = constant”, move the constant to the local default segment. (In V3-4.) (Absent branch labels.) This would be a lot of work for a small improvement, though.
So as an aside here, if you’re not aware, Bocfel “expects” to be built with link-time optimization enabled. This doesn’t matter at all for most games, but when you have an operation that runs in 4 seconds, you might be able to bring it down to somewhere around … 3 seconds maybe!
If you’re using gcc or clang:
make OPT='-O3 -flto=auto' [other make options here]
I think the main culprit in slowing things down, if you’re not using LTO, is memory access. Most of the time memory is accessed via functions which do little more (sometimes nothing more) than indexing into an array. These are actual functions, not macros, so you pay the penalty of a function call every time memory is accessed. That is, unless you turn on LTO and allow inlining across source files.
As I mentioned, for regular use, nobody’s really going to notice this. But you might!
I have argued that ZIL is more imperative than it looks, due to running in a completely non-heap-based environment. (vaporware has disagreed with me on this. :) The syntax is functional (Lisp-y) but nearly all of it could be transformed line-by-line to Inform with no loss of power.
The libraries are more different. But not, interestingly, because of Inform’s object-oriented model. The Inform library doesn’t rely on classes or inheritance at all.
The difference is that Inform breaks up its parsing and action logic more; an object can customize its behavior in many ways by providing different properties. ZIL tends to stuff all logic into one ACTION function per object.
Both. For example, the standard function for printing text, TELL, started out as a macro which was copied from one game to another, and eventually moved into the compiler. Others, like PRSO?, were never moved into the compiler. And others, like PHRASE in Shogun and N-S/E-W in Zork 3, were written for one game and never copied elsewhere. Post-Infocom examples include PROB and MAZE-ROOM in Advent.
These macros are valid MDL code, but they’re in a .zil file which is fed into the ZIL compiler. How exactly to describe the relationship between MDL and ZIL is a philosophical question I wrestle with myself as I write documentation, but there isn’t any version of ZIL that doesn’t have MDL in it; this is as “raw” as it gets. At the end of the day, when you write a game in ZIL, this is the language you’re writing in.
That’s fair. Most ZIL code (at least the part that isn’t MDL) is certainly written in an imperative style. I’d say the difference is still relevant in that there’s still a conceptual leap going from Inform to ZIL: to understand why various ZIL idioms work (e.g. whether an ACTION routine will actually prevent the default action) requires knowing some details about how expressions are evaluated and how their values flow around that aren’t made explicit in the code like they would be in Inform.
Notably, ZIL(F) is the one Z-machine language I know that still has a full-featured macro system. Dialog has a limited one that’s used to implement the grammar system, but it only works on atomic values and lists (not on elements of Dialog syntax), which made it a real pain to do the “highlight if on Z-machine, link if on web” stuff in Wise-Woman’s Dog.