The problem
One of the less-elegant features of Dialog is removable word endings. There’s a rather crude way you can specify endings to be optionally removed from words, and then if a word is not found in the game dictionary, the engine will try removing those endings and re-parsing. The results are unintuitive even to people who know how it’s supposed to work.
Linus proposed an alternative, way back when: a predicate that breaks a word into a recognized prefix and an arbitrary suffix, starting with the longest prefix. In other words, a multi-query with ARCHITECTUREXYZ would first split it into ARCHITECTURE and XYZ, then ARCHITECT and UREXYZ, then ARCH and ITECTUREXYZ, and finally A and RCHITECTUREXYZ.[1] A regular, non-multi query would only return the first one, ARCHITECTURE and XYZ.
This always seemed like a fantastic idea to me, but I was never comfortable enough with Dialog’s internals to attempt it. But now we’re about two years into the community-maintained fork, and I think I have a way.
A tangent
What Dialog calls a “dictionary word” is not actually the same thing as a Z-machine “dictionary word”, as seen in Inform. A Dialog “dictionary word” is actually an abstraction over three different data types:
- A Z-machine dictionary word, stored as an index into the dictionary
- An unrecognized word, stored as a list of characters
- An extended word, stored as two pieces: the “required” part (a Z-machine dictionary word) and the “optional” part (a list of characters).
Extended words were created to avoid this situation on Z-machine:
> EXAMINE UNEMPLOYMENT POSTER
You don’t see any unemploym poster here.
Z-machine dictionary words are limited to nine characters, so if the player types something longer than nine character, it’s stored as an extended word: the first nine characters are the required part, and any further characters are the optional part.
Conveniently, when comparing dictionary words, only the required parts are compared. So UNEMPLOYMENT and UNEMPLOYM compare equal. This will be relevant later.
My solution
I propose something like this block of pseudocode.
def find_prefixes(word, output):
let head = split_word(word) # produces a list of characters
let curr = head
while True:
if curr == []: fail
push_choice(0, next) # The next time we fail, jump to `next` instead
curr = curr.tail
def next(head, curr):
pop_choice() # Remove the last choice point from the stack (i.e. the one we just used)
firsthalf = split_list(head, curr)
# If head is [a b c ... x y z] and curr is [x y z]
# Then firsthalf is a new list [a b ... v w]
# Thus [a b ... v w] [x y z] together comprise head
# Note: curr must be a pointer into the head list! Not a separate list!
output = make_extdict(firsthalf, curr) # fails if not possible
In other words, this would produce one or more extended words, each of which has the prefix as the required part and the suffix as the optional part.
This would all be implemented in Dialog’s IR, which means the implementation wouldn’t be set in stone. We could switch to a more efficient implementation at any time as long as the behavior remained intact, and any improvements would apply to Z-machine, debugger, and Å-machine alike.
It would, however, require one new IR instruction (and thus one new Å-machine instruction; the Å-machine hews close to Dialog’s IR):
MAKE_EXTDICT
value value dest
arg3 <- make_extdict(arg1, arg2)
# Takes a list of words and a list of characters
# Attempts to make an extdict, joining the first list into the required part, and the second list into the optional part
# If it cannot (i.e. the first list doesn't form a valid dictionary word), fails
def make_extdict(required, optional):
req_word = join_words(required)
if req_word.tag != dict: fail
result = TOP
TOP += 2
heap[result] = req_word
heap[result+1] = optional
return <extdict, result>
We might also want an opcode that splits an extdict into its required and optional parts…or we could even build that into the behavior of MAKE_EXTDICT, by analogy with MAKE_PAIR (which both creates and splits lists).
So…
What do people think about this—both the concept and the implementation? Is this worth spending an opcode on?
Assuming A, ARCH, ARCHITECT, and ARCHITECTURE are in the game’s dictionary. ↩︎