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.
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.
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.
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.
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!
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ââŠ
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.â
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.
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.
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.
> 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.
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.