Inspired by the recent Mini-Cluedo project, I’m wondering how difficult it would be to build a minimal Inform 6 game without using any standard libraries—coding everything from scratch.
I know the Inform compiler handles the creation and encoding of grammar lines automatically, depending on the grammar version constant. But does it also handle the unpacking and decoding of those lines? Or does that have to be done manually by the library author, going through the bytes of each line and checking for a match?
(I know the token-matching will have to be done by the library, but I’m not sure if this goes all the way down to parsing out the arrays by hand.)
If you want to look at I6 code that unpacks the grammar table, see the UnpackGrammarLine() routine in the library. Or this test program: grammar-dump-test.inf, which unpacks the grammar tables and prints them out in human-readable form.
The minimal parser is not too hard to build. As you might imagine, looking at how many people built it in the early 1980s. Often in assembly language or BASIC.
It’s a question of how many Inform 6 features you’re willing to jettison. Just thinking about it quickly:
Only support the noun grammar token.
Only support simple name property lists. No parse_name.
Forget about disambiguation entirely. Same goes for multiple objects and ALL.
Define compass directions as regular verbs.
On the world-model side:
Find objects using the simplest idea of scope: iterate object lists starting at the room, recursively, skipping closed containers.
Implement I6’s before / after properties, more or less as documented.
At this point you have something that would handle your basic “NORTH / GET LAMP / INV / PUT LAMP ON TABLE” sort of game. It would not be a lot of work, I shouldn’t think.
And for a really basic Scott-Adams-style parser, it would probably be easiest just not to use the compiler’s grammar-line functionality at all—just dispatch on the first word of the parse buffer and let each verb routine do what it wants with the rest of the line. It would mean throwing out most of the syntactic sugar and compiler features around actions, but for a minimal example those are overkill anyway.
I have adapted Michael Taylor’s Magic Mirror with Inform 6 without the libraries. I mention this because, unlike Dogstar, I don’t use switch statement to target verbs, but a minimal grammar table with a piece of magic code I’d found I don’t know where (probably not compatible with V3 grammar):
I have stripped down I6 Adventureland to its barebones, ending with a 4812 bytes “shellScott”, which includes the standard library, ending with a 62976 bytes shellshott.z5 (but I think that is still compilable with inform 6.1.5 against library 6/1 and 6/2, the last ones capable of generating .z3 story files, but I haven’t tested it)
Also, I have even fooled with all the other alternative libraries, and what generates the smallest .z3 code is mInform, with a 23.552 shell.z3, followed by PunyInform with 26.112 bytes minimal.z3 and Inform 6.15-lib6/2 with 37.888 bytes shell.z3 (but this earlier inform compiler don’t have the nifty OMIT_UNUSED_ROUTINES flag…)
As one can see, there’s more than enough not-so-fat steak to be stripped down to the more or less bare bones, a solution far better than building-up (and having to “reinvent the wheel”) IMO.
I’ve found that looking through Adventuron’s manual helps wrap one’s brain around basic parser functionality.
You may want to incorporate a spell checker using the Damerau - Levenshtein Distance with Adjacent Transpositions formulas. There’s some really compressed code versions online that return an accuracy score. Allow 1 or 2 mistakes depending on the length of the matching command (“adjacent transpositions” count as one mistake when flipping two characters while typing)… and you might have a forgiving, but not too forgiving, parser.
The Dragon and the Troll doesn’t use a library, and it includes its own parser, as you can see in the code:
Array e->1 3 2 2 6 1 3 1 2 6 5 5 5 5 5 4 5 5 1 7 4 1 5 1 1;Array g->5 7 7 6 4
13 15 15 15 6 9 11 11 11 10 5 7 6 4 4 9 11 10 9 2;Array c-->"stick" "rope"
"key" "ring" "sword" "treasure" "mountain pass" "mountain pass" "gate to the
north" "gate to the south" "dragon" "troll";Array y-->3 11 5 21 0 20 12 17 18
19 25 1;Array b-->"on a plain" "in the forest" "in the desert" "in a swamp" "in
the mountains" "by the river" "in a cave";Array u-->'n//' 's//' 'e//' 'w//'
'take' 'jump' 'drop' 'kill' 'give' 'i//' 'quit' 'q//';Array h-->"Road is
blocked" "What???" "You can't take it!" "Where is it?" "You pick it up" "You
drop it" "The rope hangs across the river" "Nothing happens" "You kill the
dragon with the sword." "You unlock the gate.";Array f->43;Array t->26;Array w
->1 2 4 8;Array d-->1 (-1) 5 (-5);Array k->3*12;[ps a;@output_stream 3 f;
print (string) a;@output_stream -3;];[main p q r m a s x v;print"The Dragon and
the Troll^^Steal the treasure^";for(x=0:x<12:x++){ps(c-->x);for(p=0:p<3:p++)k->
(3*x+p)=f->(2+p);}p=1;r=1;m=1;.i;s=m;m=0;if(s==0)m=1;.j;if(m){print(string)h-->
(m-1),"^";p=a;m=0;}print"^You are ",(string)b-->(e->(p-1)-1),"^You see ^";for(x
=0:x<12:x++)if(y-->x==p)print(string)c-->x,"^";if(q&&p==10 or 5)print(string)h
-->6,"^";if(r){r=0;print"The troll wants its ring back.^";}g->17=6;g->18=4;if(y
-->2<0){g->17=7;g->18=6;if(p==18 or 19){print(string)h-->9,"^";}}print"You can
go ";for(x=0:x<4:x++)if(g->(p-1)&(w->x))print(address)u-->x,",";new_line;.n;
print "? ";f->0=40;t->0=6;read f t;v=0;if(t->1>0)for(x=0:x<12:x++)if(t-->1==u
-->x)v=x+1;if(v==0){print"Eh?^";jump n;}a=p;switch(v){1,2,3,4:m=(g->(p-1))&(w->
(v-1));if(m)p=p+(d-->(v-1));jump i;5:if(t->1>1)for(x=0:x<12:x++){if(k->(3*x)==f
->(t->9)&&k->(3*x+1)==f->(1+t->9)&&k->(3*x+2)==f->(2+t->9)&&y-->x==p){m=5-2*(x>
5);if(m==5)y-->x=-1;jump j;}}m=2;jump j;7:if(t->1>1)for(x=0:x<12:x++){if(k->(3*
x)==f->(t->9)&&k->(3*x+1)==f->(1+t->9)&&k->(3*x+2)==f->(2+t->9)){m=6-2*(y-->x>-
1);if(m==6)y-->x=p;if(p==10&&x==1&&y-->x==p){m=7;q=1;g->9=14;y-->1=0;}if(p~=1||
x~=5)jump j;.z;print"When you give the chest to the troll he gives you a
kingdom and lots of gold. You live happily ever after.^";jump o;}}m=2;jump j;6:
p=p-(5*(p==17))+(5*(p==12));if(y-->0>0||a==p)m=8;jump j;10:print"Inventory:^";
for(x=0:x<12:x++)if(y-->x<0)print(string)c-->x,"^";jump j;8:if(p==25&&y-->10==p
&&y-->4<0){m=9;g->24=10;y-->10=0;jump j;}print"Something kills you.";9:if(p==1
&&y-->3<0){y-->3=0;y-->4=-1;print"You get a sword by the troll.^";jump j;}if(p
==1&&y-->5<0)jump z;m=8;jump j;}.o;print"^Press [RETURN] to exit.^";read f t;];
The game compiles to either z3 or z5, producing a 3 KB story file, with padding, if you use the latest compiler with the standard space-saving options, e.g:
So, if i understand, the idea to write a standalone parser in Inform6. If think fundamentally, this isn’t too difficult. It could actually be fairly competent.
Not knowing enough about what’s available in Inform6, here’s what i think you’d need:
Some good string processing routines.
A way to represent a tree.
A dictionary of words with their parts of speech.
A recursive grammar that builds the tree.
The means of term resolution.
so (5). This would be the bit where your tree of terms is resolved against the world. How feasible is it to query the world. (5) needs to bind all nouns in the tree to objects in the world.
Finally there must be an existing way the command is dispatched to the game to execute it. You’d need to convert your bound tree into this format and execute.
However, a project like this would benefit a lot by being more generic than Inform6.
This crystalizes what’s wrong with this framing of the problem: writing a parser-with-no-library is exactly the same task as writing a parser library.
You could take any of the examples above, chop out the game-specific objects, and you’d have a parser library. It might have fewer features than PunyInform, it might be clumsier to extend for new games, but it would do the same basic job as Puny or the standard lib.
Yeah, this problem is entirely a proof of concept: I’m wondering how much I6 code it would take to write a minimal parser that still uses the compiler’s data structures. The goal isn’t to make anything actually usable beyond “is it possible”, like the Mini-Cluedo project.