Fixing chained letter-remover commands in CM?

In the thread about the Before Processing a Command extension, @draconis mentions:

Counterfeit Monkey will choke if you WAVE X-REMOVER AT SOMETHING THEN WAVE Y-REMOVER AT SOMETHING

This got me thinking about how to lift this limitation with the help of the new extension.

First of all, the Counterfeit Monkey code uses a custom global text variable, player-command-substitute, instead of the player's command, in some places. This does not really matter to the current discussion, but may trip up anyone wanting to experiment with the current Github code.

Basically, the current code looks for the regular expression (.)-remover in the player input, and sets the variable current setting of the letter-remover to the letter in the first capturing group, that is, whatever letter comes before the text “-remover”. It then replaces the text “[current setting of the letter-remover]-remover” in the player input with simply “letter-remover”, which the parser will understand.

The problem with the command WAVE X-REMOVER AT SOMETHING THEN WAVE Y-REMOVER AT SOMETHING is that the regular expression will return the second match, “Y-remover”, set the current setting of the letter-remover to “Y”, and then change the player command to WAVE X-REMOVER AT SOMETHING THEN WAVE LETTER-REMOVER AT SOMETHING. This wiil be rejected by the parser, because the word “X-REMOVER” is not understood.

How would you go about getting around this?

EDIT: If it isn’t obvious, changing every occurrence of the regular expression (.)-remover to ‘letter-remover’ will make the parser accept the command(s), but it will still not work correctly, because the letter-remover will initially be set to Y when we asked to set it to X.

2 Likes

I see Draconis has liked this post without saying anything. That makes me think what I’m going to say could be off-target, or that I’m misunderstanding what you’re saying / asking, because if this is a question requiring answering, wouldn’t he have answered it?

If you are asking: How do I allow CM to accept and correctly execute WAVE X-REMOVER AT SOMETHING THEN WAVE Y-REMOVER AT SOMETHING


 I’d have thought adding the extension is probably already the whole solution.

The extension breaks the sequence into two commands that will be processed one after the other as if the player typed only the first, then only the second.

WAVE X-REMOVER AT SOMETHING
WAVE Y-REMOVER AT SOMETHING

The first command will do all its biz, setting the global. Then the second command will do its biz, setting the global separately. The initial line the player typed combining the two is never subjected to one regex, which I’m reading as the source of the problem.

I say this as a guy already using the extension and enjoying its awesome powers.

EDIT: I originally pasted LETTER instead of Y-remover.

-Wade

Oh no, the like without responding is just because I don’t fully grok Inform’s regular expression machinery. The problem is that the extension doesn’t quite break it into two commands like that: when you type X THEN Y THEN Z, the extension gives you X THEN Y THEN Z; then Y THEN Z; then Z. (Since this is how Inform’s parser underlyingly handles it.)

I think the issue is this line:

if N matches the regular expression "(.*) (.)-remover (.)*":

Regexes (in Inform and elsewhere) are greedy by default, so the (.*) consumes as much of the string as possible, giving you only the last instance of (.)-remover. If you replace that first part with (.*?), it’ll make that first group consume as little of the string as possible instead—giving you the first instance of (.)-remover.

But I’m not entirely confident in that.

1 Like

Oh, I see. Is there anything special about THEN or is it like that for all the ways to chain up commands?

The non-greedy fix sounds best if it works. In my head I thought of an alternative where you temporarily work on a version of the command with everything from THEN on snapped off, but that also gets more complex if you have to check for other ways to chain commands. And whether or not THEN could appear earlier in a legitimate command.

-Wade

Same for all of them. The trick is that the parser doesn’t divide by THEN and . right away—it starts parsing from the beginning of the sentence, and if it sees a THEN or ., it takes that as a cue to stop parsing and save the rest of the command for later.

Which is rather inconvenient for things like this, but that’s how it’s worked since before I was born, so it’s infeasible to change now!

1 Like

Changing to non-greedy patterns should work.

But it seems to me that it would be best if it could be done without regular expressions entirely. It’s easy to specify Understand "a-remover" as the letter-remover. for every letter. Of course what is then needed is a way to then notice when the letter remover’s letter is changed. While there are several parsing activities to deal with solving ambiguity etc, it doesn’t seem like there’s an easy way once something has been parsed to get back the snipped that the player had used? A new activity called something like “after identifying an object” could be very useful for this situation, as well as situations where players can provide names for objects, or for tracking how the player refers to things, etc. Maybe just a rulebook, I don’t think it would make sense to refer to “before after identifying an object”


The Subcommands extension does something like that, giving every object a snippet property that’s set when it appears in a command.

1 Like

That is a good point. Will this work for the verb versions U-REMOVE MOURNING DRESS as well? For that, I suppose we would then have to add a separate action for each of the 26 letters in order to know which letter should be removed, and corresponding grammar lines to make the variant REMOVE U FROM MOURNING DRESS work. I think I ran into a hard upper limit of actions at one point, or was that grammar lines?

On the other hand, being able to give the error response “Only the 26 letters of the English alphabet are available to the letter-remover” would presumably still require a regular expression.

You can make verb synonyms, what we’re still missing (to my knowledge) is a way to easily go back from the parsed verb to the snippet. It’s worse for verbs though as verbs really specify the whole command syntax rather than just one word.

While using non-greedy regexes works in some cases, there is still the complication that while we previously assumed we only had to deal with one occurrence of (.*?) (.)-remover (.)* or (.?)-remove.*, we are now trying to handle cases like U-REMOVE MOURNING DRESS THEN WAVE R-REMOVER AT T-SHIRT.

Our new code with non-greedy regexes will first look for (.*?) (.)-remover (.)*, match R-REMOVER, assume that we want to change the setting of the letter-remover to R, and then fail to replace the verb “U-REMOVE” with “LETTER-REMOVE”, because it can’t find the string “[current setting of the letter-remover]-remove” in the input (it is looking for R-REMOVE instead of U-REMOVE.) The parsing will then fail with “That’s not a verb I recognize.”

1 Like

If anyone wants to have a go at this, I’ve create a branch with my attempts so far here:

Note that it contains some commented-out debug and work-in-progress stuff.

Conveniently, Subcommands also makes a “subcommand of the verb” snippet variable for exactly this purpose!

(Well, not exactly this purpose. The idea is if you want TAKE to mean GET in all cases, but also print a little explanatory message if someone tries TAKE PILL.)

Hmm. What if, instead of using regexes to match the entire input, you first look for (.)-remove, record the text matching subexpression 1, then replace that text with letter-remove? Then a single regex will do the verbs and the nouns, so you’ll always get the first instance.

Thanks, that finally solved it.

Turned out the hardest part was making it only replace the first instance of \b.-remove. I had to do this:

	let C be the substituted form of the player's command;
	if C matches the regular expression "\b.-remove":
		let N be "[C]";
		let pre be "[C]";
		let post be "[C]";
		replace the regular expression ".*?\b(.)-remove.*" in N with "\1";
		replace the regular expression "(.*?)\b.-remove.*" in pre with "\1";
		replace the regular expression ".*?\b.-remove(.*)" in post with "\1";
		now C is "[pre]letter-remove[post]";

And then change the text of the player's command to C.

2 Likes

The next challenge is making SET LETTER-REMOVER TO U THEN WAVE IT AT DRESS work.

The problem seems to be the [text] token in the grammar line Understand "set [letter-remover device] to [text]" as tuning it to, which swallows all of the following text (“U THEN WAVE IT AT DRESS”.)

Yeah, that part is unfortunately hardwired into Inform’s parser at a level that’s very difficult to change. The easiest (though not easy!) solution would be to write an I6 GPR that only accepts a single word, and use that in place of [text] in the Understand lines.

Hmm
that honestly might be a good extension to make in general. Add a [word] Understand token that accepts any single word and stores it into the topic understood.

2 Likes

Okay, this should do it.

Include (-
[ WORD_TOKEN ;
	if(NextWordStopped() == -1) return GPR_FAIL;
	consult_from = wn-1;
	consult_words = 1;
	return GPR_PREPOSITION;
];
-).

The Understand token word translates into I6 as "WORD_TOKEN".

As a demonstration:

Selecting is an action applying to one topic. Understand "select [word]" as selecting.
Carry out selecting: say "Selected: [topic understood].".

>select
I didn’t understand that sentence.

>select a
Selected: a.

>select a b c
I only understood you as far as wanting to select.

>select a then jump
Selected: a.

You jump on the spot.

2 Likes

Wow, that works. Amazing!

> set letter-remover to u then wave it at dress then set letter-remover to r then wave it at shirt

You flick our thumb over the small knob: we now have a U-remover.

There is a flash of yellow light, and the mourning dress turns into a morning dress. An outfit of striped trousers and fancy coat, such as men sometimes wear to fancy weddings in the morning.

[Your score has gone up by one point.]

You flick our thumb over the small knob: we now have an R-remover.

No doubt this would be a cogent statement about the commercialization of the body, if it weren’t for the fact that T-SHIT doesn’t describe anything anyone with a functional colon has ever heard of.

I never noticed that non-adaptive “You” before, but it seems it has always been there.

1 Like

If this passes all the test cases, I’ll throw it into a minimal extension and put it on the repository later. Seems useful in general for things like dial settings.

It does not matter in this case, but perhaps still of interest: If I try to replace the [text] token with [word] in this grammar line:

Understand “remove [word] from [something]” or “letter-remove [word] from [something]” as letter-removing it from. Letter-removing it from is an action applying to one topic and one visible thing.

I get this error:

The grammar you give in ‘Understand “remove [word] from [something]” or “letter-remove [word] from [something]” as letter-removing it from’ is not compatible with the Letter-removing it from action (defined as ‘applying to one topic and one visible thing’) - the thing you suggest this action should act on has the wrong kind of value.