Looping through a list of topics?

Hi there, me again.

Trying to work myself through some I7 fundamentals…

In section 18.33 of the documentation there is the following example:

After reading a command: 
    if the player's command includes "please": 
        say "Please do not say please."; 
        reject the player's command.

I thought it could be useful to go through a list of texts, instead of just checking on “please”, but I cannot figure out how.

This is what I tried:

After reading a command:
	repeat with word running through {"text1","text2","text3"}:		
		if the player's command includes "[word]":
			say "No, don't say [word] to me!";
			reject the player's command.

So I wanted to use " if (snippet) includes (topic) :".

However, word seems to have a different type, I assume text.

Attempts to declare word before the repeat command failed. Do I really need to use Understand here? But how to deal with the list then? Or is there a type conversion that allows me to do some kind of cast between text and topic?

Topics are most easily handled in a table, and you can run through the rows of a table.

As I was recently schooled, I7 will recognize a literal double-quoted string as a topic in a phrase that calls for a topic.

But a literal list like { "text1", "text2" } will always be considered a list of texts. And it might occur to someone to think “OK, fine, I’ll declare something as a list of topics variable and then I7 will know that the double-quoted strings I add to that list are topics”… but you’re not allowed to declare something to be a list of topics variable (or a topic variable).

This is just context for why Zarf’s answer is the best (and only) choice for looping through topics.

This works, though. You don’t always actually need a topic, per se.

After reading a command:
  repeat with word running through { "text1", "text2", "text3" } begin;
    if "[the player's command]" matches the regular expression "\b[word]\b" begin;
     say "No, don't say [word] to me!";
     reject the player's command;
   end if;
  end repeat;

[Correction after the fact: As detailed below, one can use temporary topic variables or temporary lists of topics just not global ones.]

4 Likes

I was just banging my head against my own code sample in response to this that I couldn’t get to work and wondering why.

Thanks, Zed!

1 Like

In this case the desired test is to check text (item on forbidden word list) against a snippet (player’s command), it seems. The phrases in WWI 20.5 Matching and exactly matching seem applicable. Does this do what you want?

After reading a command:
    repeat with word running through {"text1","text2","text3"}:
	    if the player's command matches the text word: [note "matches the text"; see WWI 20.5 Matching and exactly matching]
		    say "No, don't say [word] to me!";
		    reject the player's command.

I think it may be a little less costly than the regular expression route.

2 Likes

Definitely less costly, but the regexp allows checking that it’s surrounded by word breaks. If it’s desirable to respond to any command that includes text1 (etc.) as a substring, the text match is better. If it’s significant to distinguish text1 from text19, the regexp is better.

2 Likes

As always, thank you for the clarification.

In the beginning, I got a bit lost in I7, but I think the reason is that I7 is a combination of the language itself, and the standard extensions that already define a „standard world“ (which is good, otherwise each author would have to define all the basic things again from scratch).

To focus on the language first, I work myself through the I7 guide for programmers. And in the introduction, it is said that I7 is „strongly typed“. So in this example, we face an exception, which was probably introduced to make it easier for non-programmers. Still, I wonder how I could have found out that it‘s not possible to define a list of topics, or the exception that sometimes the text type counts as topic type.

Are you aware of any other exceptions from the „strongly typed“ rule? Is there a list somewhere?

I have a feeling it would be good to know these things before getting started, to avoid too many „try and error“ cycles. :wink:

Just fyi, after checking on the documentation for lists again, I thought of giving it another try, but this version causes a runtime problem:

Your room is a room.

After reading a command:
	Let insensitive-language-list be a list of topics;
	Let insensitive-language-list be {"text1","text2","text3"};
	repeat with word running through insensitive-language-list:		
		if the player's command includes word:
			say "Don't say that to me!";
			reject the player's command.

After starting the game, I entered text1, and got the following runtime error:

*** Value handling failed: impossible allocation ***

*** Run-time problem P49: Memory allocation proved impossible

This is not an answer to the list question(s), but just to throw another possibility into the mix:

The Lab is a room.

Unnecessary-word is a kind of value.
The unnecessary-words are foo, bar, bla, blub.

After reading a command:
	if the player's command includes "[unnecessary-word]":
		say "You don't need to say words like [the unnecessary-word understood].";
		reject the player's command.
1 Like

@StJohnLimbo: Of what type is unnecessary-words in your example?

I guess foo is an object of kind unnecessary-word, right?

It was surprising to me that I7 checks on all the unnecessary-words in the if-clause, but I assume that’s similar to other checks, where no explicit object is part of the check, but only its class (its kind) is given. Hm.

Hi all,

Just to complete the list of possibilities, I came across another variant in the programming guide that is very very short - using slashes to indicate alternative texts (topics) to match in the command:

Your Room is a room.

After reading a command:
	if the player's command includes “foo/bar/blubb”:
		say “I don't want to hear the word [bold type][matched text][roman type] from you![line break]” instead.

I don’t think that’s an exception? Everything in I7 has a type.

[In this line, "please" is a topic]
if the player's command includes "please": 

[In this line, "please" is a text.]
let foo be "please";	

I7 is sometimes flexible about deciding what a quoted string means, but it always decides this at compile time. (In a weakly typed system, the type of a variable is not figured out until runtime.)

2 Likes

It’s a “kind of value”. In traditional programming languages, we would say “an enum”.

1 Like

^ What zarf said. :slight_smile:

Basically yes, but insert “value” instead of “object”. Objects (in I7 parlance) are a different top-level concept, see the Kinds Index in the IDE (and WI 4.5. Kinds of value).

In Ron Newcomb’s guide there’s a short section on “Named values” (p. 53 in the PDF version which you linked to above).

Cf. 17.9. Understanding kinds of value, quoting from there:

In many cases, if K is the name of a kind of value, then Inform automatically makes an Understand token called “[K]” which matches only values of K. An example is “[number]”, which matches text like 203 or SEVEN. There is a chart of the kinds of value in the Kinds index for a project, showing which ones can be understood in this way.

In particular, any newly created kind of value can always be understood. […]

1 Like

I don’t think you’ll find anyplace it’s documented that you can’t make a list of topics. But in WI 22.1 you’ll find the list of base kinds, and the list of constructions (e.g., list of K), and the statement that variables can only be defined as one of those base kinds or something constructed from them, and topic isn’t a base kind. Similar information is in “Chart” in the Project Index.

But what about the topic understood? That’s a variable! Yes, it’s a variable whose kind is snippet. I7 keeps you on your toes.

That’s not really another variant. Topics are represented internally as the exact same sort of thing that Inform 7 creates when you write Understand statements. You can write Understand "paper" as the document. or Understand "dusty/paper" as the document. The case without slashes just happens to have only one possible thing to match.

A dirty secret, except not that dirty or that secret: a deep understanding of Inform 7 requires understanding Inform 6. If you read the DM4, you’ll read about GPRs: general parsing routines. Topics are GPRs. “text1/text2/text3” as a topic compiles to something like:

[ Consult_Grammar_99
    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;
        group_wn = wn;
        .group_156_1_1; wn = group_wn;
        if (NextWordStopped() ~= 'text1') jump group_156_1_2;
        jump group_156_1_end;
        .group_156_1_2; wn = group_wn;
        if (NextWordStopped() ~= 'text2') jump group_156_1_3;
        jump group_156_1_end;
        .group_156_1_3; wn = group_wn;
        if (NextWordStopped() ~= 'text3') jump Fail_1;
        .group_156_1_end;
        if ((range_words==0) || (wn-range_from==range_words)) return rv;
        .Fail_1; rv = GPR_PREPOSITION; wn = original_wn;
    return GPR_FAIL;
];

And that ultimately is why you can’t convert text to topics – I7 can’t create a function dynamically. And why it’s not meaningful to speak of converting topics to texts – a GPR could be arbitrarily complicated, and even in the “just match one string or not” case I7 can’t reach inside a function to see that that’s the case.

1 Like

This should reasonably produce a compilation error, by the way; I’d count it as a bug that it doesn’t.

It’s not an error, and in fact you can make use of such a list – if you copy the entries from a table.

Table of Sybil's Replies 
Topic	Muse
"calliope"	"epic poetry"
"clio"	"history"
"erato"	"love poetry"

After asking the Sybil about a topic:
	Let topic-list be a list of topics;
	repeat through the Table of Sybil's Replies:
		add the topic entry to topic-list;
	repeat with T running through topic-list:
		if the topic understood matches T:
			instead say "The Sybil says that is in the list.";
	say "Eh?"

This is an overcomplicated way of doing “After asking the Sybil about a topic listed in the Table of Sybil’s Replies…”, but it’s valid and it works.

1 Like

Hunh. I sit corrected – thanks. It’s a compilation error if you try to define a global list of topics variable; I assumed the same should apply to temporary variables. And I now see it allows use of a temporary topic variable, too, though trying to define a global one generates a compilation error.

…and to (I hope) clarify, unnecessary-word is a kind of value, but "[unnecessary-word]" is a topic in the context of if the player's command includes "[unnecessary-word]". So Understanding kinds of value explains how/why this works.

Something I’m at least pretty sure is true (and I hope someone will correct me if I’m not): “topic” and “token” are different names for the same thing.