A player kindly sent me a bug report of an error when they try:
WAIT FOR 1
(this isn’t the right syntax; you can WAIT 1 or WAIT FOR 1 MINUTE/HOUR/etc — I’m using Puny’s waittime extension)
I made a small version of the code that shows the error:
!%++puny
Constant INITIAL_LOCATION_VALUE = TheRoom;
Include "globals.h";
Include "puny.h";
[ Initialise; ];
Object TheRoom "The Room" with description "The room";
[ WaitLongSub; ];
Verb 'foo' * 'for' number 'minutes' -> WaitLong;
When you try to FOO FOR 1, it produces the error I think you wanted to say "foo for [** Programming error: Class (object number 1) has no property short_name to read **] Class minutes". Please try again.
I could fix this by adding another grammar line that matches “foo for {number}”; that wouldn’t be unreasonable — but I have a bunch of grammar that rely on numbers, and I worry that these might exhibit similar kinds of parser bugs when players are close-but-not-correct with verb-with-number-noun syntax.
I tracked this down to _PrintPatternSyntax and can see that while it special-cases several token types/data, it doesn’t for numbers, and so it tries to treat it like an object.
I’ve modified the code so that it works (see big obvious spacing showing the two lines I added):
[ _PrintPatternSyntax p_pattern p_noun _num_preps _token _type _data _i _count _token_value;
! write what pattern we expected, something like:
! "I think you wanted to say "put all in something". Please try again."
PrintMsg(MSG_PARSER_BAD_PATTERN_PREFIX);
print (verbname) verb_word;
_count = (p_pattern->0) / 8;
for(_i = 1: _i <= _count: _i++) {
! for(_token = p_pattern + 3: _token->0 ~= TT_END: _token = _token + 3) {
_token = p_pattern + _i + _i;
_token_value = _token -> 0;
_type = _token_value & $0f;
_data = _token -> 1;
! check if this is a new list of prepositions
if(_token_value == TOKEN_FIRST_PREP or TOKEN_SINGLE_PREP) _num_preps = 0;
if(_type == TT_PREPOSITION) {
! only write the first item in a list of alternative prepositions
if(_num_preps == 0) print " ", (address) #adjectives_table-->_data;
++_num_preps;
} else {
print " ";
if(p_noun == 0) {
if(_type == TT_ROUTINE_FILTER && #preactions_table-->_data == ADirection) {
print (string) SOMEDIRECTION_STR;
} else if(second == 0) {
! if(_type == TT_OBJECT && _token->2 == CREATURE_OBJECT) {
if(_type == TT_OBJECT && _data == CREATURE_OBJECT) {
print (string) SOMEONE_STR;
} else {
print (string) SOMETHING_STR;
}
} else print (name) second;
} else {
! only part that Joel changed is that I added this:
if(_type == TT_OBJECT && _data == NUMBER_OBJECT) {
print noun;
} else if(noun ~= 0 && metaclass(noun) ~= ROUTINE) {
p_noun = 0; ! avoid repeat (we don't need p_noun anymore)
if(parser_all_found) print "all"; else print (name) noun;
! } else if(_type == TT_OBJECT && _token->2 == CREATURE_OBJECT) {
} else if(_type == TT_OBJECT && _data == CREATURE_OBJECT) {
print (string) SOMEONE_STR;
} else {
print (string) SOMETHING_STR;
}
}
}
}
PrintMsg(MSG_PARSER_BAD_PATTERN_SUFFIX);
];
that works, and it feels like a low-risk change mid-SpringThing. If anyone more familiar with parsing can see anything risky about this, or has other advice on a better way to solve this, it’d be much welcomed.