Manipulating user input with "topic understood"

Hi Folks, I’m pretty new to I7 and I suspect my programming background may be hurting me.
After tearing out some handfuls of hair, I finally got something work, but now I’m back to tearing out more…
The following works:

[code]A selector has some text called current setting. The current setting is usually "K".
There is a selector in the cave. "There is a generator here". The description is "The generator is marked K/G/O. It is currently set to [current setting].".
Understand  "sel" and "select" as selector.
Setting is an action applying to one thing and one topic.
Understand "set [selector] [text]" as setting.
Check setting something when the noun is not a selector: instead say "That doesn't have a setting.".
Check setting something when the current setting of the noun is the topic understood: instead say "It is already on that setting.".

Carry out setting:
	now the current setting of the noun is the topic understood;
	Report setting: say "The selector is now set to [current setting].".

What I can’t seem to get to work is manipulating the text returned by “topic understood”. Every attempt to use it in if/otherwise statements fails, despite the many variations I’ve tried. What I’d like to do is take the user’s text and: 1) make sure it is in the form of k, g, or o and 2) convert it to upper case before storing it into the current setting. I7 doesn’t seem to want me to change anything in “topic understood” and I’ve not found how to cast it into anything like a string variable that I could mess with. I’m sure I must be missing something quite simple, but I have searched the docs, the internet, and even spent some very interesting time with chatGPT, but all resulted in abject failure. If this keeps up, I will surely turn into a bald man, but at least my back is hairless already…

Try this:

The cave is a room.
A selector has some text called current setting. The current setting is usually "K".
There is a selector in the cave. "There is a generator here". The description is "The generator is marked K/G/O. It is currently set to [current setting].".
Understand  "sel" and "select" as selector.
Setting is an action applying to one thing and one topic.
Understand "set [selector] [text]" as setting.
Check setting something when the noun is not a selector: instead say "That doesn't have a setting.".
Check setting something when the current setting of the noun is the topic understood: instead say "It is already on that setting.".

Check setting:
	let T be the topic understood;
	if T exactly matches the regular expression "k|g|o|K|G|O":
		continue the action;
	otherwise:
		say "bad setting." instead;

Carry out setting: 
	let T be the topic understood;
	now the current setting of the noun is T in upper case;

Report setting: say "The selector is now set to [current setting].".

The short answer is: the topic understood isn’t a text, it’s a snippet. (Though Inform has a kind of value named topic, in one of its fits of perversity, the topic understood isn’t one.) Your code would work with:

Check setting something when the current setting of the noun is "[the topic understood]": instead say "It is already on that setting.".

It may seem paradoxical that now the current setting of the noun is the topic understood does work, but Inform knows that current setting is a text. Most of the time it prefers to give you a compilation error when there’s a kind of value mismatch, but this is one of the conversions it’ll do automatically.

2 Likes

As an aside, this is probably not good style.
Understand "set [selector] [text]" as setting. prevents the parser from matching anything other than the selector to a typed command in order to generate a setting action.

Consequently, Check setting something when the noun is not a selector: instead say "That doesn't have a setting.". will never fire, because the parser will never generate an action that is setting something when the noun is not a selector.

So you could just delete the redundant second line, and rely on the parser to allow only the selector to be set.

But this approach has drawbacks. For example, if there is a radio in the room and the player types ‘set radio channel 4’ the parser will confusingly respond ‘You can’t see any such thing’ because it can’t find anything nearby that matches the [selector] token.

What you generally want is to rely on the parser (and its limited ability to respond flexibly and appropriately to incorrect input) as little as possible, allow it to generate reasonable albeit ‘incorrect’ actions, then respond to those with your own Before Instead or Check rules.

So, in this case, perhaps change your action declaration to:

Understand "set [something] [text]" as setting.

and leave your check rule in place to catch 'something’s that are not the selector, displaying a more nuanced response.

As a second aside, Inform already recognises the command ‘set’ but only as “set [something] to [topic]”, the action thus created being ‘setting it to’.

Consequently, as you’ve currently got things, if the player types ‘set selector to K’ this will generate the inbuilt ‘setting it to’ action, which by default does nothing except respond ‘No, you can’t set that to anything.’

Generally it’s better to make use of built in actions if you can rather than rolling your own- in this case just recode everything so that it applies to the existing ‘setting it to’ action.

There are some nuances to this. If you look under the Index tab then select under the Actions Index A1 Grouped, you will find the ‘Setting it to’ action listed under ’ Standard actions which always do nothing unless rules intervene’. Click on the little magnifying glass by ‘Setting it to’ and you’ll get to a summary of the existing action, including the following text:

Setting something to some text (past tense set it to): applying to one thing and one topic

The Standard Rules define this action in only a minimal way, blocking it with a check rule which stops it in all cases. It exists so that before or instead rules can be written to make it do interesting things in special cases. (Or to reconstruct the action as something more substantial, unlist the block rule and supply carry out and report rules, together perhaps with some further check rules.)

What we want to do is implement the last thing suggested, i.e. unlist (or disable) the existing block rule - (which produces the ‘No, you can’t set that to anything.’ response before aborting all setting it to actions). You can see from the list of rules controlling this action that the pesky blocking rule we need to be rid of is called the ‘block setting it to rule’. So we can either unlist it The block setting it to rule is not listed in any rulebook or disable it The block setting it to rule does nothing. Now we are free to impose our own rules on the setting it to action:

The block setting it to rule does nothing.

Understand "set [something] [text]" as setting it to.

Check setting when the noun is not a selector: instead say "That doesn't have a setting.".
Check setting when the current setting of the noun is the topic understood: instead say "It is already on that setting.".

Carry out setting:
	now the current setting of the noun is the topic understood;

Report setting: say "The selector is now set to [current setting].".

Now ‘set selector K’ and ‘set selector to K’ will both work, generating the ‘setting it to’ action.

Note that when witing rules for ‘setting it to’ (or similar) you can write either ‘Check setting…’ or ‘Check setting something to…’ but not ‘Check setting something…’.

To avoid inadvertently writing actions or commands that clash with inbuilt actions, you can search in the Index under the Actions Index tab either for the action itself, or for the command word (in this case ‘set’) that initiates it under Index → Actions Index → Commands, which lists alphabetically all Inform’s understood command words- including any new ones your story has defined- and the actions they initiate.

5 Likes

Ehhhhhhxcellent - this works flawlessly. Thank you!
The “continue the action” statement is interesting and am wondering if its inclusion is just intended as a convenience or perhaps considered good style. I refactored the code to:

if not (T exactly matches the regular expression "k|g|o|K|G|O"):
	say "bad setting." instead;

This concise form works fine also, but there may be some deeper nuance to “continue the action” I’m not grokking.

To tell the truth, I had trouble negating

if T exactly matches the regular expression "k|g|o|K|G|O"

so I just switched the if-else around.

Ah, now another two of my errant tumblers have clicked into place. Thanks for clarifying the text vs snippet distinction. The silent type cast had led me into a false assumption about how topic understood should work. I shall delve further into the topic topic.

Inform has three relevant types here. Since you’ve talked about “strings” I’m going to describe them in a C-esque style.

“Text” can be either a string (an array of characters) or a routine that prints something. This is how a “text” variable can hold things like [if] and [one of]. If you want to convert it from the second form into the first, you can say “the substituted form of”; this is irrevocable (you can’t go back from the string to the routine that generated it). This happens automatically if something important for the routine would go out of scope, or if you store something that’s not text to a text variable. “[The player’s command]” is a routine that looks at the player’s command and prints it; the substituted form of “[the player’s command]” is a string holding its value at the current moment (and never changing hereafter).

(In previous versions of I7, these were two different types: routines were “text” and strings were “indexed text”. This distinction was abolished some time ago because casting between them was considered unnecessarily confusing.)

A “snippet” is a segment of the player’s command, stored as two numbers: the first word, and the number of words. “The player’s command” and “the topic understood” are the main ways to get snippets. You can convert a snippet into a string by storing it to a text variable.

A “topic” is a routine that takes a snippet and returns true or false. These aren’t generally manipulated as values; they’re only created when you have a table column named “topic” or write rule headers about an action with a [text] token. For example:

Instead of setting the calculator to "something/anything" or "[number]":

This creates a topic that returns true if the relevant snippet is “something”, “anything”, or a number. (The “instead” rule will run if “the topic understood” matches this topic.) The main thing you can do with a topic is check if a snippet matches it, or includes it (i.e. if some sub-snippet of the snippet matches it). In the latter case, you can also get the sub-snippet that matched, which is sometimes useful for fancy parsing hacks.

2 Likes

This is a situation where you it would be easier to not mess around with texts or topics at all. If you define a kind of value, the parser will parse it for you.

The cave is a room.

A kgo-setting is a kind of value. The kgo-settings are K, G, O.

A selector has a kgo-setting called current setting. The current setting is usually K.

There is a selector in the cave. "There is a generator here."
The description is "The generator is marked K/G/O. It is currently set to [current setting].".
Understand  "sel" and "select" as selector.

Sel-setting is an action applying to one thing and one kgo-setting.

Understand "set [thing] [kgo-setting]" as sel-setting.
Understand "set [thing] to [kgo-setting]" as sel-setting.

Check sel-setting when the noun is not a selector:
	say "That's not a selector."

Carry out sel-setting a selector:
	now the current setting of the noun is the kgo-setting understood;
	say "Set to [the current setting of the noun]."
	
Check setting a selector to:
	instead say "You can set that to K, G, or O."

Here we define a new action which specifically matches the kind-of-value kgo-setting, which can only have the values K, G, and O.

Notice that this catches every possible case with a sensible error message!

>set sword to xyzzy
No, you can't set that to anything.

This is the default failure message for the built-in setting action. You get this for free.

>set sword to k
The sword is not a selector.

The “check sel-setting” rule catches this case.

>set selector to xyzzy
You can set that to K, G, or O.

The “check setting” rule catches this case. This is the built-in setting action, but it only fires when the last word is not K, G, or O.

>set selector to o
Set to O.

>x selector
The generator is marked K/G/O. It is currently set to O.

And here is where it succeeds.

5 Likes

No worries - I learned another thing. :smiley:
In the spirit of refactoring, I find this also works: now the current setting of the noun is the topic understood in upper case; The intermediate step (in the carry out section) let T be the topic understood can be omitted. Looks like another case of automatic type casting Zed mentioned above.

Thank you for this, good doctor! I have reimplemented according to your suggestions for a more generic approach. For completeness, in case other beginners wish to use some part of this pattern, I had to alter a few more lines: All instances of Check setting something... need to be changed to Check setting something to... and Carry out setting: needs to be Carry out setting something to:

Frankly, I am a bit surprised that this whole rig functions equivalently whether I include or omit “to” in this line: Setting is an action applying to one thing and one topic versus Setting to is an action applying to one thing and one topic I had expected less forgiveness from Inform.

Well-spotted!

There are many places where the natural language conceit is implemented through cheats that admit nonsense as valid syntax. Instead of jumping sounds much better than Instead jumping but the implementation strategy is simply: a rulebook name (in that context) can be followed by an optional “of” or “for”, which also allows things that sound bad, like Before of jumping.

I hadn’t known about this one, but now I see that Syntax.preform has:

<action-optional-trailing-prepositions> ::=
        ... to

It many cases, like the above, it’s a deliberate design choice. But my guess is that I7 development and testing has historically placed a much higher priority on verifying that expected, intended usage works than detecting incorrect usage and giving meaningful errors. A recurring class of bug is the compiler accepting without comment something that by all rights should have resulted in an error. Sometimes it ends up being a no-op in the program, sometimes not.

Thanks zarf, this seems a useful simplification to know, but I understand it less than the more complicated one delineated earlier in the thread. Two issues I’m puzzling over:

>set selector i
I didn't understand that sentence.

I had expected this to succeed with the output You can set that to K, G, or O. because of this: Understand "set [thing] [kgo-setting]" as sel-setting. I tried using variations of The block setting it to rule does nothing. to circumvent built-in interference, but to no avail.

>set selector g
Set to G.

I had expected this to fail, as see I no place where the lower case user input is converted to upper case, yet this succeeds by divining my intent somehow without explicit instructions(!)

Offhand comment: The challenge I fear that awaits me with a system like Inform seems to be the entangled nature of the ‘whole’ where, at least to a tyro like myself, many aspects of the gestalt need to be retained and considered with the accrual of components. Part of the difficulty is that I can’t yet reliably judge what I can safely forget.

The entire parser is case-insensitive by default. The only reason you need to worry about case in the earlier examples is that you’re pulling out raw strings and working with them directly.

>set selector i

This does not match any grammar line at all, so it stops with a parser error rather than an action.

The relevant grammar lines are:

Understand "set [something] to [text]" as setting it to.

Understand "set [thing] [kgo-setting]" as sel-setting.
Understand "set [thing] to [kgo-setting]" as sel-setting.

It can’t match the first line (from the Standard Rules) because there’s no “to” in the command. It can’t match the other lines because “i” isn’t a kgo-setting. So there’s no action to pick.

You could change this by adding a new Understand line:

Understand "set [something] [text]" as setting it to.

…although it might not be worth worrying about this.

Thanks for the clarification. This method works and I understand nearly all of the foregoing… except for one last small point. You included a line that was not present earlier: Understand "set [something] to [text]" as setting it to. I checked and determined that the behavior of the system does not change with or without it; when I included the omitted “to” case with: Understand "set [something] [text]" as setting it to., all of the scenarios function properly. Is it correct to conclude that the default behavior of the parser assumes an included “to”?

In the preamble to a rule applying to an action declared in the form ‘<verbing> it <preposition>’ e.g. ‘setting it to’ you have to include the preposition
(i) if you specify ‘something’ or some other named object or description of objects in place of the ‘it’ placeholder, or
(ii) if you specify what comes after the preposition.

(in other words, you need to include the preposition for anything other than the plain ‘verbing’ on its own)

You can also use ‘it’ in place of ‘something’ or a named object or specification of objects as long as you don’t specify what comes after the preposition. (i.e. if you’re just restating the action declaration e.g. ‘setting it to’) and only in the specific action rulebooks (Check, Carry out, Report)

So any of these should compile:

Report setting:
Report setting to:
Report setting it to:
Report setting something to:
Report setting the selector to:
Report setting to "K/G/O":
Report setting something to "K/G/O":
Report setting the selector to "K/G/O":

but not

Report setting it:
Report setting "K/G/O":
Report setting something:
Report setting the selector:
Report setting it "K/G/O":
Report setting it to "K/G/O":
Report setting something "K/G/O":
Report setting the selector "K/G/O":

EDIT: all of these compiling examples also work if you add a ‘when…’ clause.

EDIT2: since some of these forms are equivalent, the only 4 forms you really need to remember are:

Report setting:
Report setting to <whatever>:
Report setting <whatever> to:
Report setting <whatever> to <whatever>:

summarised as:
(i) just the plain verb without the preposition
(ii) with the preposition, omitting (and therefore not specifying) either what comes before it or what comes after it
(iii) with the preposition, specifying both what comes before it and what comes after it.

1 Like

That line (Understand "set [something] to [text]" as setting it to.) appears in the Standard Rules, the basic library that’s included in all Inform projects by default. That’s also where the “setting it to” action is defined (as an action applying to one thing and one topic) and is blocked off (with the block setting rule).

1 Like

Actually, as it turns out (as detailed above) you can write ‘Report <verbing> <preposition>: ...’ for any action declared as ‘<verbing> it <preposition> is an action applying....’ - it’s not necessarily a special case for ‘to’.

Hence, for example:

Report answering that: ...

or

boondoggling it xyzzy is an action applying to two things.
Report boondoggling xyzzy: say "Kazamm!".

are both fine.