The match list

While profiling my WIP, I discovered that my Lost Items extension has a severe point of pain:

[code]Definition: A thing is sought if it is the item sought.
The item sought is a thing that varies.

Repeat with the missing item running through lost things:
	Now the item sought is the missing item;
	if the player's command includes "[any sought thing]":
		carry out the noticing absence activity with the missing item;
		rule fails;

[/code]

As you might imagine, repeatedly calling “if the players command includes…” eats up processor time in a big way. What I really wanted, and was till now too lazy to figure out, was to access the match list.

From trying out stuff based on the “Walls and Noses” example, the match list seems pretty ephemeral. Can anyone explain what happens to the match list over the course of the turn sequence? When does it get wiped out, truncated, rebuilt, etc? Lost Items operates during the printing a parser error activity, and that’s when I need to generate and access a list of lost items.

Thanks!

Maybe someone could explain exactly what’s going on in the FindInParseList function in the Walls & Noses example:

[ FindInParseList obj i k marker; marker = 0; for (i=1 : i<=number_of_classes : i++) { while (((match_classes-->marker) ~= i) && ((match_classes-->marker) ~= -i)) marker++; k = match_list-->marker; if (k==obj) rtrue; } rfalse; ];

What exactly does i represent? Is it an object id or a class id? What is match_classes?

Parser errors can be printed at several points in the parser cycle, depending (obviously) on which error you’re talking about.

The best way to understand the match_classes array is to type “trace 4” and then try some ambiguous commands. Well, maybe not the best way…

The idea is that match_list (an array of object ids) is grouped into “classes” of identical items. (Identical to the player, that is.) This grouping may or may not line up with I6 classes. If you define a bunch of identical coins, they will be one class in this sense (because the command “GET COIN” is not ambiguous, it just picks an arbitrary coin). If no two items in the list are identical, every item winds up in its own class.

The groups are numbered from 1 to number_of_classes. Then, match_classes–>N gives the group number for the Nth item (which is in match_list–>N). Except it’s positive for the first item in the group, and negative for all following items in the same group.

(Glad you asked yet?)

As to why FindInParseList is written that way – I have no damn idea. You’d think it could just search match_list and ignore match_classes. The double-loop idiom you see there is great for printing a list of the classes (used in the “which do you mean…?” parser message) but I don’t know what it’s doing there.

Okay, that’s usable. But it seems like the match list changes much more than I’d expect it to, and often contains weird things - sometimes too much and sometimes too little. I get the feeling that by the time I inspect it, the data has been corrupted.

I’m also not sure whether calling “the [snippet] includes” or “the [snippet] matches” even uses the match list at all. How would I begin to formulate a function that loads the match list (or some data structure) with all the objects matching a particular topic with a particular snippet?

No, snippets don’t touch the match list at all. They compare literal text to subsets of the input buffer.

I don’t have code for you; maybe later when I’m in front of my compiler again…

The most efficient way to do what you want is to extend every verb grammar with an extra line with more expansive scope:

Understand “examine [any lost thing]” as a mistake (“That’s lost.”)
[Repeat for every single verb.]
[That’s probably not working I7 code, but you get the idea.]

This is not convenient to do in an extension, of course. Or outside of an extension either.

If you know the point in the input where the noun starts, you may be able to rig up some I6 code that calls MatchTextAgainstObject for every lost object. That will build you a match_list.

Thanks! I’ll try MatchTextAgainstObject. I’d like to apply this to my Objects Matching Snippets extension too.

So I’m looking at MatchTextAgainstObject, and it looks like the manipulations of match_from and related variables are a bit intricate. Any advice about how to manage them?

I only know this stuff by looking at the code, myself.

I’m hoping that, at the time you want to do this, match_from and indef_nspec_at have already been set by the parser. Those are the two variables of interest to MatchTextAgainstObject. If they’re not meaningfully set, you may have to stick a hook into the parser earlier on and save their values.

I’m not sure what I would invoke in order to set them, but I’m going to proceed on the assumption that if I push them onto the stack and then set them myself, I can get things back to the way they should be.

I’m starting to think MatchTextAgainstObject doesn’t quite do what I want, though - I can use match_from to specify a starting point, but by the time I can check match_length to see if it fits the snippet, it’s already been added to the match list.

I’m going to dig a little deeper to see if I can simply return true/false rather than using the match_list.

Trying to compare the way normal parsing works with SnippetIncludes. SnippetIncludes takes a GPR as an argument, right? I’m a little fuzzy on what that means, but I just discovered that my auto.inf uses a function called “Consult_Grammar_408” when testing for the “sought” item used in Lost Items and Objects Matching Snippets. Do Consult_Grammar_* functions have the same profile as GPR_* functions?

Still working on this. I keep getting lost…

What I know so far:

Man, this is confusing. The scope functions are weirdly overloaded. I’m starting to get the feeling that “scope_reason” is an extremely hacky method of implementing callbacks. So when an “[any …]” token is encountered when parsing a grammar line, scope_reason is probably PARSING_REASON, and down in the guts of the scope loop, MatchTextAgainstObject is called. But when the token is encountered via a “snippet matches ‘[any …]’” phrase, the scope reason is something else.

Near the top level, a “Scope_Filter_*” will trigger a scope loop. This suggests a direction of inquiry, at least.

Okay… so a Scope_Filter_* for an “any …” token loops over objects… aaarhgh! Too much overloading! Sometimes it returns a value and sometimes it doesn’t! scope_stage, parser_reason… I’m sick of globals! These variables should not be global!

[ SnippetMatches snippet topic_gpr rv; wn=1; if (topic_gpr == 0) rfalse; if (metaclass(topic_gpr) == Routine) { rv = (topic_gpr)(snippet/100, snippet%100); if (rv ~= GPR_FAIL) rtrue; rfalse; } RunTimeProblem(RTP_BADTOPIC); rfalse; ];
and my example value of topic_gpr (actually used with SnippetIncludes in auto.inf) is:

[ Consult_Grammar_408 range_from ! call parameter: word number of snippet start range_words ! call parameter: snippet length original_wn ! first word of text parsed group_wn ! first word matched against A/B/C/... disjunction w ! for use by individual grammar lines rv ! for use by individual grammar lines ; wn = range_from; original_wn = wn; rv = GPR_PREPOSITION; w = ParseTokenStopped(SCOPE_TT, Scope_Filter_48); if (w == GPR_FAIL) jump Fail_1; rv = w; if ((range_words==0) || (wn-range_from==range_words)) return rv; .Fail_1; rv = GPR_PREPOSITION; wn = original_wn; return GPR_FAIL; ];

[ ParseTokenStopped x y; if (wn>WordCount()) return GPR_FAIL; return ParseToken(x,y); ];
ParseToken essentially boils down to this:

[ ParseToken given_ttype given_tdata token_n token i t rv; rv = ParseToken__(given_ttype, given_tdata, token_n, token); return rv; ];
And then there’s a big whole morass, which I think does this when called in this context:

[ ParseToken__ given_ttype given_tdata token_n token l o i j k and_parity single_object desc_wn many_flag token_allows_multiple prev_indef_wanted; switch (given_ttype) { SCOPE_TT: scope_token = given_tdata; scope_stage = 1; l = indirect(scope_token); if (l == 1) given_tdata = MULTI_TOKEN; else given_tdata = NOUN_TOKEN;
Okay, let’s stop right here. This is the only place where scope_token is invoked. And the only reason to invoke it is to check whether the token allows multiple objects. Which in this case, it never does. Then we throw away the value of given_tdata, which is the scope filter function that says what kind of object we’re looking for. And as far as I can tell, we never check again, anywhere up or down the call chain, whether the noun matched by NounDomain was actually one of the things we were matching for. I must be missing something, but what and where?

I don’t know what your code is trying to do. What is the token you are matching, which generated this Consult_Grammar_408 routine? Is this still “[any sought thing]”?

Yes, I’m trying to trace what happens when I run this line:

if the player's command includes "[any sought thing]":

I figure if I can identify what code produces that result, I can modify it to loop over all matching objects. I want to do it for both “includes” and “matches,” and I don’t mind looping over all possible snippets myself if I have to.

But you know, I’ve been hacking up the extension code, and that line isn’t in it any more. Maybe I just need a better example to look at.

No, that’s not it. I do the same thing in “Objects Matching Snippets”:

Does the object match a snippet (called S): if the item to match is a thing and S matches "[any matchable thing]", it does;

       if (((((Global_Vars-->11) ofclass K2_thing))) && (( (SnippetMatches(t_0, Consult_Grammar_416)) ))) { RulebookSucceeds(19, RBNO_12); rtrue;

[ Consult_Grammar_416 range_from ! call parameter: word number of snippet start range_words ! call parameter: snippet length original_wn ! first word of text parsed group_wn ! first word matched against A/B/C/... disjunction w ! for use by individual grammar lines rv ! for use by individual grammar lines ; wn = range_from; original_wn = wn; rv = GPR_PREPOSITION; w = ParseTokenStopped(SCOPE_TT, Scope_Filter_50); if (w == GPR_FAIL) jump Fail_1; rv = w; if ((range_words==0) || (wn-range_from==range_words)) return rv; .Fail_1; rv = GPR_PREPOSITION; wn = original_wn; return GPR_FAIL; ];

[ Scope_Filter_50 obj o2; switch (scope_stage) { 1: rfalse; 2: obj=noun; objectloop(noun ofclass Object && (((noun ofclass K2_thing) && ((Adj_37_t1_v9(noun)))))) { o2 = noun; noun = obj; suppress_scope_loops = true; PlaceInScope(o2, true); suppress_scope_loops = false; noun = o2; } noun=obj; 3: nextbest_etype = NOTINCONTEXT_PE; return -1; } ];
We’re still passing a basically identical scope filter to ParseTokenStopped, which ends up at ParseToken__ the same way.

Here’s a question: What’s the difference between direct invocation of a function variable using round brackets (as seen in SnippetMatches) and using indirect (as seen in ParseToken__)?

rv = (topic_gpr)(snippet/100, snippet%100);
l = indirect(scope_token);

Oh, wait, I see what I’ve been missing.

scope_token is a global variable. So it exists beyond the scope of ParseToken__. In order to find out how ParseToken__ uses it, I have to trace every other function that ParseToken__ calls.

Can I scream now?

At least I’ve made conceptual progress: A Scope_Filter_* isn’t really a single function. It’s an object with three completely different methods. Original Parser calls them “does this allow multiple objects,” “please add eligible objects to scope,” and “please report the noun not making sense in this context.” Trouble is, which method gets called depends on a global variable, which, if you look through the code, isn’t always set in the same context as the function call.

Okay, I’m calm.

I ran a trace 6 on my WIP and typed a command that would invoke the rule quoted above. It turns out DoScopeAction is called on all objects with scope_reason PARSING_REASON, and the object is added to the match list using MatchTextAgainstObject as you might expect. So in theory, it should be possible to do what I need with something very close to SnippetMatches.

I imagine what happens next, though, is that the match list is reset to some earlier value. Is that a reasonable assumption?

[edit] Yes indeed. That’s exactly what is done by the code I omitted earlier from ParseToken. Now I think I have a direction to take with this project…!

Here’s my proposed code so far:

[code]Include (-
[ SaveMatchGlobals i t;
@push match_from; @push token_filter; @push match_length;
@push number_of_classes; @push oops_from;
for (i=0: i<number_matched: i++) {
t = match_list–>i; @push t;
t = match_classes–>i; @push t;
t = match_scores–>i; @push t;
}
@push number_matched;
parsetoken_nesting++;
];

[ RestoreMatchGlobals i t;
parsetoken_nesting–;
@pull number_matched;
for (i=0: i<number_matched: i++) {
@pull t; match_scores–>i = t;
@pull t; match_classes–>i = t;
@pull t; match_list–>i = t;
}
@pull oops_from; @pull number_of_classes;
@pull match_length; @pull token_filter; @pull match_from;
];

[ LoopOverObjectsMatchingSnippet func snippet scope_filter i rv;
@push parsetoken_nesting;
parsetoken_nesting = 0;
SaveMatchGlobals();

wn=1;
rv = ConsultGrammarForObjects(scope_filter, snippet/100, snippet%100);
if (rv == GPR_FAIL) {
	RunTimeProblem(RTP_BADTOPIC);
	rfalse;
}

for (i=0: i<number_matched: i++) {
	indirect(func, match_list-->i);
}

while (parsetoken_nesting) { RestoreMatchGlobals(); }
@pull parsetoken_nesting;
rtrue;

];

[ ConsultGrammarForObjects
scope_filter ! call parameter: a description of objects
range_from ! call parameter: word number of snippet start
range_words ! call parameter: snippet length
original_wn ! first word of text parsed
group_wn ! first word matched against A/B/C/… disjunction
w ! for use by individual grammar lines
rv ! for use by individual grammar lines
;
wn = range_from; original_wn = wn; rv = GPR_PREPOSITION;
w = ParseTokenStopped(SCOPE_TT, scope_filter);
if (w == GPR_FAIL) jump Fail_1; rv = w;
if ((range_words==0) || (wn-range_from==range_words)) return rv;
.Fail_1; rv = GPR_PREPOSITION; wn = original_wn;
return GPR_FAIL;
];
-)[/code]

I can’t test this yet because I’m missing some key hooks. Most importantly, I don’t know how to transform a description of objects into a scope filter. I got worried when I started looking for descriptions of objects in auto.inf - it looks suspiciously like dynamic memory usage. Any tips on this aspect?