A command goes through six hands between the keyboard and the thing being done: the line reader (READ_LINE), the tokeniser (TOKENISE), the parser's state machine (PARSE_COMMAND), the pattern matcher (MATCH_PATTERN), the object matcher (MATCH_AND_TRY) and the one routine that does anything for anyone (DO_ACTION). The overview says what each is for; this page follows one real line through all of them, with what each left in its buffers.

The line followed

Typed at the first prompt of a game, in Bag End, where Thorin and Gandalf are and the player holds the curious map (Gandalf's first step gives it):

SAY TO THORIN "QUICKLY OPEN THE CHEST" THEN READ THE CURIOUS MAP AND DROP IT

It has an order in quotes with an adverb in it, THEN, two verbs that are synonyms (SAY, READ), an adjective, AND in front of a verb, and IT. The game was run in SkoolKit's simulator when this page was built, the line put into the input buffer as scripts/hobbit_drive.py does, and every table below was read from the machine by a probe on the address named, just before the instruction there ran. What the game printed, from PRINT_CHAR past its gate (the player's commands, then the rest of the world's turns, Thorin's among them):

you talk to thorin.

you examine the curious map.
there seem to be some symbols on it but
you cannot read them.

you drop the curious map.
gandalf opens the round green door.
thorin opens the wooden chest.
gandalf goes east.
thorin says " hurry up ".

1. The line

READ_LINE takes keys into INPUT_LINE as plain ASCII, echoing them, and ends the line with a carriage return ($0D). Nothing is done to the words yet. The cursor is only registers -- HL walking the line, B counting the room left, 128 to start -- which is how a command can be put in from outside (see the notes' driving.md).

2. Words into tokens

The main loop calls TOKENISE once per word, from $6D31, and keeps each two-byte token in TOKENS. A full stop, a comma or a quote is a token by itself (PUNCTUATION_TOKEN). Anything else is copied as letter codes -- the low five bits of each character, so A is 1 -- into TYPED_LETTERS ($707A), up to the first character below $40, and looked up by MATCH_WORD: the first letter picks a bucket from WORD_INDEX, and each entry in it is unpacked and compared by LETTERS_AGREE over the shorter of the two lengths, until one agrees. The table is each call, with the candidates it unpacked (read at $6F72, where each one starts) and the token it returned (in B and C at $6D34):

TypedLetter codesCandidates triedEntry takenClassToken
SAY19 1 254: S, SAND, SAVE, SAYSAY ($6584) → TALK$3 verb36 90
TO20 1515: TAKE, TALK ... (11 more) ... TIE, TOTO ($66D1)$7 preposition76 D1
THORIN20 8 15 18 9 1410: TAKE, TALK ... (6 more) ... THIN, THORINTHORIN ($66B4)$5 noun56 B4
"---$9 quantifier, pronoun or game command90 00
QUICKLY17 21 9 3 11 12 251: QUICKLYQUICKLY ($650C)$0 adverb05 0C
OPEN15 16 5 148: OF, OFF ... (4 more) ... ONTO, OPENOPEN ($64B4)$3 verb34 B4
THE20 8 55: TAKE, TALK, TANGLED, THAT, THETHE ($669F)$8 article or the like86 9F
CHEST3 8 5 19 2012: CACHE, CAMP ... (8 more) ... CELLAR, CHESTCHEST ($6122)$5 noun51 22
"---$9 quantifier, pronoun or game command90 00
THEN20 8 5 146: TAKE, TALK ... (2 more) ... THE, THENTHEN ($66A2)$B thenB6 A2
READ18 5 1 43: RAVENHILL, RAVINE, READREAD ($6530) → EXAMINE$3 verb32 3E
THE20 8 55: TAKE, TALK, TANGLED, THAT, THETHE ($669F)$8 article or the like86 9F
CURIOUS3 21 18 9 15 21 1922: CACHE, CAMP ... (18 more) ... CUPBOARD, CURIOUSCURIOUS ($6163)$6 adjective61 63
MAP13 1 163: MAGIC, MAN, MAPMAP ($642C)$5 noun54 2C
AND1 14 47: A, ACROSS ... (3 more) ... AN, ANDAND ($605B)$A andA0 5B
DROP4 18 15 1619: D, DALE ... (15 more) ... DRINK, DROPDROP ($61E8)$3 verb31 E8
IT9 208: I, IN ... (4 more) ... IS, ITIT ($639E)$9 quantifier, pronoun or game command93 9E
(end)---$C end of the lineC0 00

Two things happen here that nothing downstream ever sees. A synonym is resolved: SAY's entry ends with a link to TALK, and READ's to EXAMINE, and the token carries the word linked to, so from here on the game only knows TALK and EXAMINE. And the buckets are only grouped by initial letter, not sorted, so a common word can be found late: CURIOUS was the 22nd C-word unpacked.

The same routine, called on single words on a fresh machine, shows its rules for short and long words: a typed word may be shorter than the entry (an abbreviation, taken as soon as the letters agree, so the first in the bucket wins), or longer, but only if the entry has four letters or more and the next candidate does not also agree:

TypedCandidatesLast candidateTokenWhy
EXAM14EXAMINEEXAMINE (token $323E)shorter than the entry: an abbreviation
INV6INVENTORYINVENTORY (token $3392)the same
CUR22CURIOUSCURIOUS (token $6163)an abbreviation takes the first entry that agrees, in bucket order
SWORDS46SWORDSWORD (token $5680)longer than the entry, which has five letters, and nothing after it agrees as well
INT5INTOINTO (token $138E)IN agrees but is too short to be stretched (under four letters); INTO then takes it as an abbreviation
MAPS10the last, MOUNTAINS; then the bucket endednot a word ($D0)longer than MAP, which has only three letters
EXAMINING16the last, EYES; then the bucket endednot a word ($D0)its seventh letter disagrees with EXAMINE's
XYZZY0no word begins with that letternot a word ($D0)no word begins with X

So the tokens for the whole line, as the parser finds them in TOKENS at $6D8A: the class in the top nibble, the dictionary offset from $6000 below. The main loop has made one change: at the closing quote it put a full-stop token first ($B0 $00), so that what is said ends as a sentence.

AtTokenClassWord
$709C36 90$3TALK
$709E76 D1$7TO
$70A056 B4$5THORIN
$70A290 00$9(a quote)
$70A405 0C$0QUICKLY
$70A634 B4$3OPEN
$70A886 9F$8THE
$70AA51 22$5CHEST
$70ACB0 00$B(a full stop, put in by the main loop)
$70AE90 00$9(a quote)
$70B0B6 A2$BTHEN
$70B232 3E$3EXAMINE
$70B486 9F$8THE
$70B661 63$6CURIOUS
$70B854 2C$5MAP
$70BAA0 5B$AAND
$70BC31 E8$3DROP
$70BE93 9E$9IT
$70C0C0 00$C(the end)

3. Word classes, and a handler for each

The class is two bits of the entry's first byte and two of its second. CLASS_DISPATCH ($75C1) turns it into an index into PARSER_CLASSES and jumps there; the handlers are the parser. Read from the game (the word counts from the dictionary, synonyms included):

ClassWords ofHandlerWordsSome of them
$0adverbPARSE_ADVERB13CAREFULLY, FEEBLY, FORCEFULLY, PLEASE, QUICKLY, QUIET, ...
$1in or intoPARSE_IN2IN, INTO
$2directionPARSE_DIRECTION19DOWN, EAST, NORTH, NORTHEAST, NORTHWEST, SOUTH, ...
$3verbPARSE_VERB59ATTACK, BURN, CARRY, CLIMB, CLOSE, CROSS, ...
$4verb of motionPARSE_GO2GO, RUN
$5nounPARSE_NOUN121ARM, ARROW, AXE, BARD, BARREL, BAY, ...
$6adjectivePARSE_ADJECTIVE96BACK, BARREN, BEORNS, BEWITCHED, BIG, BLACK, ...
$7prepositionPARSE_PREPOSITION16ACROSS, AFTER, AT, FOR, FROM, INSIDE, ...
$8article or the likePARSE_ARTICLE11A, ALREADY, AN, ANOTHER, ARE, CAN, ...
$9quantifier, pronoun or game commandPARSE_SPECIAL14ALL, EXCEPT, HELP, IT, LOAD, NOPRINT, ...
$AandPARSE_AND1AND
$BthenPARSE_THEN1THEN
$Cend of the linePARSE_END--

4. The state machine, and the frames it fills

PARSE_COMMAND parses one command and returns to the main loop, which obeys it and calls it again while there is more on the line. It takes tokens through NEXT_TOKEN; register E says what may come next, each handler setting and clearing its bits. A handler that wants to know what follows reads on itself, so not every token goes through the class dispatch: a preposition looks for another preposition, an adjective for the noun after it. The words go into a 24-byte frame at COMMAND_FRAME (more frames, for a sentence that needs them, lie below it, from FRAMES): the verb, an adverb or a direction, and two noun phrases of two prepositions, the noun and two adjectives, each a two-byte word reference, low byte first. Articles are noted and dropped.

Each command below is one call of PARSE_COMMAND: every token read, by which routine (the caller of NEXT_TOKEN), E as it was then, and where the class dispatch sent it; then the frame as OBEY found it at $6D96.

Command 1: TALK TO THORIN, and an order

TokenClassRead byEDispatched to
TALK$3PARSE_COMMAND (PARSE_NEXT_WORD)$FF: verb, adverb, article, 1st phrase free, 2nd phrase freePARSE_VERB
TO$7PARSE_COMMAND (PARSE_NEXT_WORD)$FD: adverb, article, 1st phrase free, 2nd phrase freePARSE_PREPOSITION
THORIN$5PARSE_PREPOSITION$FD: adverb, article, 1st phrase free, 2nd phrase free-
(quote)$9PARSE_COMMAND (PARSE_NEXT_WORD)$BD: adverb, article, 2nd phrase freePARSE_SPECIAL
QUICKLY$0PARSE_COMMAND (PARSE_NEXT_WORD)$FF: verb, adverb, article, 1st phrase free, 2nd phrase freePARSE_ADVERB
OPEN$3PARSE_COMMAND (PARSE_NEXT_WORD)$FB: verb, article, 1st phrase free, 2nd phrase freePARSE_VERB
THE$8PARSE_COMMAND (PARSE_NEXT_WORD)$F9: article, 1st phrase free, 2nd phrase freePARSE_ARTICLE
CHEST$5PARSE_COMMAND (PARSE_NEXT_WORD)$E9: 1st phrase free, 2nd phrase freePARSE_NOUN
(full stop)$BPARSE_COMMAND (PARSE_NEXT_WORD)$B9: article, 2nd phrase freePARSE_THEN
(quote)$9PARSE_COMMAND (PARSE_NEXT_WORD)$FF: verb, adverb, article, 1st phrase free, 2nd phrase freePARSE_SPECIAL
THEN$BPARSE_COMMAND (PARSE_NEXT_WORD)$BD: adverb, article, 2nd phrase freePARSE_THEN

The frame, as OBEY found it:

OffsetFieldBytesWords
0-1verb90 06TALK
2-3adverb or direction00 00-
4-7phrase 1: prepositionsD1 06 00 00TO
8-9phrase 1: nounB4 06THORIN
10-13phrase 1: adjectives00 00 00 00-
14-17phrase 2: prepositions00 00 00 00-
18-19phrase 2: noun00 00-
20-23phrase 2: adjectives00 00 00 00-

The quote is a class $9 token with no word, which PARSE_SPECIAL finds in slot 0 of SPECIAL_WORDS: its handler is SPECIAL_QUOTE. At the opening quote it calls ORDER_BEGINS, which keeps the sentence so far aside and parses what follows in a fresh frame with IS_ORDER set -- QUICKLY, OPEN, THE, CHEST, and the full stop the main loop added, which ends the order as THEN would but, inside quotes, carries straight on. At the closing quote SPECIAL_QUOTE files each frame parsed since into a free slot of ORDERS, marked $FF, puts the first sentence back and goes on. The THEN after it ends the command. So the frame OBEY gets holds only TALK TO THORIN; the order is in ORDERS slot 0, as it was at $6D96:

ByteFieldBytesWords
0whoseFFnot yet given ($FF: waiting)
1-2verbB4 04OPEN
3-4adverb0C 05QUICKLY
9-10phrase 1: noun22 01CHEST
11-14phrase 1: adjectives00 00 00 00-

Command 2: EXAMINE THE CURIOUS MAP

TokenClassRead byEDispatched to
EXAMINE$3PARSE_COMMAND (PARSE_NEXT_WORD)$FF: verb, adverb, article, 1st phrase free, 2nd phrase freePARSE_VERB
THE$8PARSE_COMMAND (PARSE_NEXT_WORD)$FD: adverb, article, 1st phrase free, 2nd phrase freePARSE_ARTICLE
CURIOUS$6PARSE_COMMAND (PARSE_NEXT_WORD)$ED: adverb, 1st phrase free, 2nd phrase freePARSE_ADJECTIVE
MAP$5PARSE_ADJECTIVE$ED: adverb, 1st phrase free, 2nd phrase free-
AND$APARSE_COMMAND (PARSE_NEXT_WORD)$BD: adverb, article, 2nd phrase freePARSE_AND
DROP$3PARSE_AND$B5: adverb, article, 2nd phrase free; just after AND-
DROP$3PARSE_COMMAND (PARSE_NEXT_WORD)$F7: verb, adverb, article, 1st phrase free, 2nd phrase free; just after ANDPARSE_VERB

The frame, as OBEY found it:

OffsetFieldBytesWords
0-1verb3E 02EXAMINE
2-3adverb or direction00 00-
4-7phrase 1: prepositions00 00 00 00-
8-9phrase 1: noun2C 04MAP
10-13phrase 1: adjectives63 01 00 00CURIOUS
14-17phrase 2: prepositions00 00 00 00-
18-19phrase 2: noun00 00-
20-23phrase 2: adjectives00 00 00 00-

AND is where the machine looks ahead. PARSE_AND passes over any more ANDs and saves a checkpoint (AND_TOKENS: the place in TOKENS, E, the frame), then ends this part as THEN would and starts a new noun phrase. The next token is DROP, a verb, and PARSE_VERB finds E's bit 3 clear -- just after AND -- so the AND joined two commands, not two noun phrases: it goes back to the checkpoint and ends the command there. DROP IT is left in TOKENS for the next call, after this command has been done.

Command 3: DROP IT

TokenClassRead byEDispatched to
DROP$3PARSE_COMMAND (PARSE_NEXT_WORD)$FF: verb, adverb, article, 1st phrase free, 2nd phrase freePARSE_VERB
IT$9PARSE_COMMAND (PARSE_NEXT_WORD)$FD: adverb, article, 1st phrase free, 2nd phrase freePARSE_SPECIAL
(end)$CPARSE_COMMAND (PARSE_NEXT_WORD)$BD: adverb, article, 2nd phrase freePARSE_END

The frame, as OBEY found it:

OffsetFieldBytesWords
0-1verbE8 01DROP
2-3adverb or direction00 00-
4-7phrase 1: prepositions00 00 00 00-
8-9phrase 1: noun2C 04MAP
10-13phrase 1: adjectives63 01 00 00CURIOUS
14-17phrase 2: prepositions00 00 00 00-
18-19phrase 2: noun00 00-
20-23phrase 2: adjectives00 00 00 00-

IT is a special word too. WORD_IT copies the six bytes kept at IT_NAME ($B6E0) -- the name of the last target, set when the command before was matched: CURIOUS MAP -- into the phrase as if they had been typed, and files it. So the frame is exactly what DROP THE CURIOUS MAP would give.

5. From a frame to an action: the sentence patterns

There is no table of verbs. OBEY calls PARSE_ACTION, which calls MATCH_PATTERN: it gathers a probe at $7958 -- the verb, then up to two words picked from the frame's phrases, their prepositions or particles -- and compares it with each 8-byte pattern in ACTION_PATTERNS by NAME_MATCHES, the same comparison the objects' names are matched with, so the order of the two picked words does not matter. The action code is the pattern's place in the list. For the three commands (read at $79C4 and $79E9):

CommandProbe, pattern and flags
1probe [TALK, TO, -]; pattern 53 at $ACF3, TALK TO; flags $28 $44
2probe [EXAMINE, -, -]; pattern 28 at $AC2B, EXAMINE; flags $08 $40
3probe [DROP, -, -]; pattern 13 at $ABB3, DROP; flags $08 $08

Each pattern's four word references carry four flag bits each, which PATTERN_FLAGS gathers into two bytes, FLAGS_FIRST_WORDS ($B71D) and FLAGS_LAST_WORDS ($B71E). What they mean is read from the code that tests them: in the first, bit 3 the pattern has a target and bit 2 an instrument (PARSE_ACTION and WOULD_WORK), bit 4 it is not narrated (NARRATE_ACTION), bit 5 the target's phrase is the one with the particle (ASSIGN_PHRASES), bit 7 the target is a place and bit 0 PATTERN_OPTION (PATTERN_OPTIONS); in the second, bits 2-3 and 0-1 are the kinds of object the target and the instrument may be (the mode FIND_NAMED_OBJECT searches in: things, characters, or either) and bit 6 the action needs light. Every pattern in the game, with those decoded (the code links to the Actions page, which has each one's handlers and the objects that carry their own):

CodeWordsLast wordFlagsTargetInstrumentTarget phraseOptionsOrdinary handler
1NORTHGO$00 $00----MOVE
2SOUTHGO$00 $00----MOVE
3EASTGO$00 $00----MOVE
4WESTGO$00 $00----MOVE
5NORTHEASTGO$00 $00----MOVE
6NORTHWESTGO$00 $00----MOVE
7SOUTHEASTGO$00 $00----MOVE
8SOUTHWESTGO$00 $00----MOVE
9UPGO$00 $00----MOVE
10DOWNGO$00 $00----MOVE
11STRIKE WITH-$0E $40thingsthings-needs lightobjects' own only
12CLOSE-$08 $40things--needs lightobjects' own only
13DROP-$08 $08either---DO_DROP
14DROP IN-$0C $40thingsthings-needs lightobjects' own only
15ATTACK WITH-$0E $44charactersthings-needs lightDO_ATTACK
16OPEN-$08 $40things--needs lightobjects' own only
17PUT IN-$0C $40thingsthings-needs lightobjects' own only
18PUT ON-$0C $40thingsthings-needs lightobjects' own only
19TAKE-$08 $40things--needs lightDO_TAKE
20TAKE OUT OF-$0C $40thingsthings-needs lightobjects' own only
21TAKE FROM-$0C $40thingsthings-needs lightobjects' own only
22TAKE OFF-$29 $40things-with the particleneeds light, PATTERN_OPTIONobjects' own only
23LOOK-$10 $40---not narrated, needs lightDO_LOOK
24LOOK THROUGH-$28 $00things-with the particle-objects' own only
25LOOK ACROSS-$28 $00things-with the particle-objects' own only
26INVENTORY-$10 $40---not narrated, needs lightDO_INVENTORY
27EAT-$08 $00things---objects' own only
28EXAMINE-$08 $40things--needs lightDO_EXAMINE
29GIVE TO-$0C $61thingscharacters-needs lightDO_GIVE
30GO THROUGH-$28 $00things-with the particle-objects' own only
31ENTER-$88 $00things--the target is a placeDO_ENTER
32GO INTO-$A8 $00things-with the particlethe target is a placeDO_ENTER
33DRINK-$08 $00things---objects' own only
34EMPTY-$08 $00things---objects' own only
35FILL WITH-$0C $40thingsthings-needs lightobjects' own only
36RUN-$00 $00----DO_RUN
37LOCK WITH-$0C $40thingsthings-needs lightobjects' own only
38UNLOCK WITH-$0C $40thingsthings-needs lightobjects' own only
39FOLLOW-$09 $04characters--PATTERN_OPTIONDO_FOLLOW
40WEAR-$08 $00things---objects' own only
41THROW-$08 $00things---objects' own only
42THROW AT-$0C $02thingseither--DO_THROW_AT
43THROW ACROSS-$0C $40thingsthings-needs lightobjects' own only
44THROW THROUGH-$0C $40thingsthings-needs lightobjects' own only
45BURN-$08 $04characters---DO_BURN
46TIE TO-$0C $40thingsthings-needs lightDO_TIE
47CUT-$08 $00things---objects' own only
48CAPTURE-$08 $04characters---DO_CAPTURE
49PULL-$08 $00things---objects' own only
50SWIM-$08 $40things--needs lightobjects' own only
51UNTIE-$08 $48either--needs lightDO_UNTIE
52CLIMB-$08 $40things--needs lightobjects' own only
53TALK TO-$28 $44characters-with the particleneeds lightDO_TALK
54CLIMB INTO-$28 $40things-with the particleneeds lightobjects' own only
55CLIMB OUT OF-$28 $80things-with the particle-DO_CLIMB_OUT
56JUMP ONTO-$29 $00things-with the particlePATTERN_OPTIONobjects' own only
57DIG-$08 $40things--needs lightobjects' own only
58SHOOT-$08 $44characters--needs lightDO_SHOOT
59CARRY-$08 $48either--needs lightDO_TAKE

The ten directions are the patterns with GO as the last word, so NORTH and GO NORTH are one action. Two flags are not accounted for by the table: bit 5 of the second byte, set for GIVE TO alone, which ASSIGN_PHRASES reads when no particle was typed; and bit 7 of the second byte, set for CLIMB OUT OF alone, which NARRATE_ACTION reads to put both particle words before the object.

6. Finding the objects: try, then do

A name is not turned into an object up front. ASSIGN_PHRASES decides which phrase is the target and which the instrument -- by where the preposition the pattern wants turns up, not by order -- and copies their nouns and adjectives to TARGET_NAME and INSTRUMENT_NAME, and the target's to IT_NAME ($B6E0) for IT. Then MATCH_AND_TRY goes through the objects whose name fits, in reach of the actor (FIND_NAMED_OBJECT, IN_REACH), and for each one runs the action through DO_ACTION with DOING_IT ($B6FA) clear: a test. Every handler makes its checks and then calls FOR_REAL, which in a test sets SUCCEEDED ($B6FB) and returns from the handler, so nothing changes and nothing is printed. The first object that would work is kept. OBEY then tests it once more, narrates it (NARRATE_ACTION), sets DOING_IT and runs DO_ACTION for real. For the three commands (fits read at $9E0F, tests at $7AED and $7AF0, the handler at $958C):

Command 1

StageWhat the machine held
target's name (TARGET_NAME)THORIN
objects whose name fitsthorin ($3F)
of those, in the actor's reachthorin
tested by MATCH_AND_TRY, DOING_IT = 0thorin: SUCCEEDED = 1
tested by OBEY, DOING_IT = 0thorin: SUCCEEDED = 1
for real: the handler DO_ACTION runsDO_TALK, from ACTION_TABLE, the ordinary handler
you talk to thorin.

Command 2

StageWhat the machine held
target's name (TARGET_NAME)CURIOUS MAP
objects whose name fitscurious map ($03)
of those, in the actor's reachcurious map
tested by MATCH_AND_TRY, DOING_IT = 0curious map: SUCCEEDED = 1
tested by OBEY, DOING_IT = 0curious map: SUCCEEDED = 1
for real: the handler DO_ACTION runsELROND_READS_MAP, from its own record ($C1E2)
you examine the curious map.
there seem to be some symbols on it but
you cannot read them.

Command 3

StageWhat the machine held
target's name (TARGET_NAME)CURIOUS MAP
objects whose name fitscurious map ($03)
of those, in the actor's reachcurious map
tested by MATCH_AND_TRY, DOING_IT = 0curious map: SUCCEEDED = 1
tested by OBEY, DOING_IT = 0curious map: SUCCEEDED = 1
for real: the handler DO_ACTION runsDO_DROP, from ACTION_TABLE, the ordinary handler
you drop the curious map.

The map's EXAMINE is the map's own: its record carries a handler for action 28, and DO_ACTION asks the object before it looks in ACTION_TABLE (see the overview). Here the player is not Elrond, so it only says the symbols cannot be read.

7. The order, on Thorin's turn

TALK TO's handler, DO_TALK, decides how many of the sentences in ORDERS the character takes: a random number up to its limit (6 for Thorin), and ASSIGN_ORDERS gives it that many of those waiting and throws the rest away. Here it was called with 3, and 1 was waiting, so slot 0's first byte became Thorin's number, $3F. On his turn in the world's turn that followed, TAKE_ORDER freed the slot and parsed the frame the way the player's are: pattern 16 (OPEN), target CHEST, which fitted wooden chest; tested, it would work, and he did it:

thorin opens the wooden chest.

The adverb was parsed and kept at offset 2 of the frame, and played no part: MATCH_PATTERN's probe is the verb and the phrases' prepositions, not offset 2 (read), so QUICKLY OPEN THE CHEST is OPEN THE CHEST. How the characters' turns work is on How the characters live.

What is confirmed, and what is not