Testing commands: linking them together by number?

Here is a snippet from testing for my IFComp 2022 entry. I broke up the test commands so that I could get to certain points without too much hassle and check them. It worked quite well. I only had to type “test w1” then enter, then up and backspace and 2, and so forth.

test w1 with "au 2/plain plea/jane g/boring box/open box/n/gad gunk/bad bunk/sad sunk/n".

test w2 with "grow grudge/bother/piss poor/n/done dorm/fun form/n/see sunk/gee junk/whee woot/pear peach/s/s".

test w3 with "e/bussed back/crust crack/n/bare beach/seep soon/s/n/reap rune/s/w".

test w4 with "w/unarm/n/go goon/cocoon/mo moon/so soon/crow croon".

test w5 with "potty pail/grotty grail/knotty nail".

test w6 with "bye bub/wordy walk/u"

test w7 with "rocking rift/flow flue/glow glue/go goo/bro brew/stow stew/crow crew/ho who/yo you/throw through".

test w8 with "shocking shift/shore shoals/night newt/kite coot/lore lols/four foals/more moles".

test w9 with "gawking gift/dumb doubt/said sos/umm out/d/hey hope/k cope".

test w10 with "grokking grift/tight tees/bright breeze/plight please/right root/s/clique claiming/bleak blaming/chic shaming/bred bros".

test w11 with "docking diffed/bad boast/rad roast/w/flight flail/might mail/sight sail/right rail/bright brute/excite exhale/thread throws".

test w12 with "despite dispute/mocking miffed/bold bend/trolled trend/mold mend/s".

test w13 with "bane bat/flain flat/splain splat/fed foes/s".

test w14 with "fret free/yet ye/set see/plus plaque/turning trite".

test wa with "test w1/test w2/test w3/test w4/test w5/test w6/test w7/test w8/test w9/test w10/test w11/test w12/test w13/test w14".

But I had the idea of “Hey, what if Inform could figure out, say, that I wanted to run tests 3 through 14 if I typed test w3-14 or some such command?”

I would be curious if there were a relatively elegant way to do this. Obviously I could grind things out with

test full2 with "test w1/test w2".
test full3 with "test full2/test w3".

etc. I could even generate “test w3-14 with test w3/…/test w14” via a python script to brute-force things.

But I’d be curious if there was a programmatically better way. I sense that might be too deep water to see if I could do this, without going deep in the weeds.

So does anyone see a relatively elegant simple way to do things?


This is that rare case where an “after reading a command” rule with a regex is probably the easiest and best solution.

No, I don’t have the regex code for you… It’d be test w([0-9]+)-([0-9]+), but then you have to pull out the groups, convert to integer, and build a new command with “test w3. test w4. test w5” etc.


Before I saw zarf’s idea, I worked up this (in 6M62):

6M62 I6 Inclusion
Include (-

Global test_range_start;
Global test_range_end;
Array test_range_prefix buffer 20;
Global test_range_prefix_len;

[ TestRange     pr_len wd_addr wd_len wd_indx chr nmbr i;
	pr_len = 0; nmbr = 0; wd_indx = 0;
	test_range_start = 0; test_range_end = 0;
	wd_addr = WordAddress(wn);
	wd_len = WordLength(wn);
	do {
		chr = wd_addr->wd_indx++;
		if (DigitToValue(chr) == NULL) {
			test_range_prefix->(WORDSIZE+pr_len) = chr;
	} until (DigitToValue(chr) ~= NULL);
	test_range_prefix->(WORDSIZE+pr_len) = 0;
	test_range_prefix_len = pr_len;
	test_range_prefix-->0 = pr_len;
	while (DigitToValue(chr) ~= NULL) {
		if (DigitToValue(chr) > NULL)
			nmbr = 10*nmbr+DigitToValue(chr);
		chr = wd_addr->wd_indx++;
	test_range_start = nmbr; nmbr = 0;
	if (chr ~= '-')
		return GPR_FAIL;
	do {
		chr = wd_addr->wd_indx++;
		if (DigitToValue(chr) > NULL)
			nmbr = 10*nmbr+DigitToValue(chr);
	} until (DigitToValue(chr) == NULL);
	if (wd_indx < wd_len)
		return GPR_FAIL;
	test_range_end = nmbr;
	!print "<test range decoded: start = ", test_range_start, ", end = ", test_range_end;
	!print ", prefix ";
	!for (i = 0: i < pr_len : i++) print (char) test_range_prefix->(WORDSIZE+i);
	!print ">^";

Extend 'test' first
	* TestRange -> MultiTestScript;

[ MultiTestScriptSub    cw t ;
	if (test_range_start < 0)
		"<Test ranges cannot begin with a negative value.>";
	for ( t = test_range_end : t >= test_range_start : t-- ) {
		cw = ConstructTestRangeWord(test_range_prefix, test_range_prefix_len, t);
		if (cw) {
			print "<Adding test ", (address) cw, " to stack...>^";
			special_word = cw;
		} else {
			print "<Test ", (PrintBuffer) test_range_prefix, t, " does not exist!>^";

Array tknbuf buffer INPUT_BUFFER_LEN;
Array tknparse   --> PARSE_BUFFER_LEN;

[ ConstructTestRangeWord prefix_buf prefix_len suffix_nmbr    i j n ;
	if (prefix_len < 1) rfalse;
	i = 0;
	while (i < prefix_len) {
		tknbuf->(WORDSIZE+i) = prefix_buf->(WORDSIZE+i);
	n = Magnitude(suffix_nmbr);
	for ( j = n: j > 0 : j-- ) {
		tknbuf->(WORDSIZE+prefix_len+j-1) = ValueToDigit(suffix_nmbr%10);
		suffix_nmbr = suffix_nmbr/10;
	tknbuf-->0 = prefix_len + n;
	!print "<tokenizing ";
	!for (i = 0: i < prefix_len + n : i++) print (char) tknbuf->(WORDSIZE+i);
	!print ">^";
	VM_Tokenise(tknbuf, tknparse);
	return tknparse-->1; ! only first word

[ ValueToDigit n c;
	if ((n<0) || (n>9)) return -1;
	c = n+'0';
	return c;

[ Magnitude nmbr o ;
	while (nmbr > 0) {
		nmbr = nmbr / 10;
	return o;

[ PrintBuffer buf     i n;
	n = buf-->0;
	for ( i = 0 : i < n : i++ )
		print (char) buf->(WORDSIZE+i); 


I don’t know if this is how you envisioned it. The test functionality works on a stack, so multiple >TEST commands in the same line (e.g. >TEST Z1. TEST Z2.) don’t get executed in the same order that they are typed. This code adds the tests in a test range to the stack in reverse order so that they occur in the naively-expected manner, but that only works within a single test range, so a command with multiple ranges (e.g. >TEST Z1-3. TEST W4-9.) probably still won’t seem right.

EDIT: Because 10.1 does not allow use of the Extend directive in I6 inclusions, a 10.1 version requires adaptation. In addition to removing the Extend block from the inclusion, the following must be added at the I7 level:

The Understand token testrange translates into I6 as "TestRange".

Rangetesting is an action applying to nothing. Understand "rtest [testrange]" as rangetesting.

To execute test range:
	(- MultiTestScriptSub(); -).

Carry out rangetesting:
	execute test range.

Note that the command in 10.1 is >RTEST for a range of tests (e.g. >RTEST Z1-3), instead of just >TEST, because otherwise the built-in >TEST functionality gets precluded. Also note that some additional safeguarding and user feedback about malformed ranges is desirable to take this from proof-of-concept to extension-ready.


Is this the kind of thing that you meant, @zarf?

To decide which number is the numeric value of (T - text):
	if T is:
		-- "0": decide on zero;
		-- "1": decide on 1;
		-- "2": decide on 2;
		-- "3": decide on 3;
		-- "4": decide on 4;
		-- "5": decide on 5;
		-- "6": decide on 6;
		-- "7": decide on 7;
		-- "8": decide on 8;
		-- "9": decide on 9;
		-- otherwise:
			decide on zero.

To decide which number is (T - text) in numerals:
	if T matches the regular expression "<^0-9>": [note: disturbs "text matching subexpression N" values]
		decide on zero;
	let n be one;
	let total be zero;
	let max be the number of characters in T;
	while n is at most max:
		now total is (10 times total) plus the numeric value of character number n in T;
		increment n;
	decide on total. 

After reading a command when the player's command matches the regular expression "test.*\b(<^0-9>*)(<0-9>+)-(<0-9>+)\b":
	let prefix be text matching subexpression 1;
	let starttext be text matching subexpression 2;
	let endtext be text matching subexpression 3;
	let start be starttext in numerals;
	let end be endtext in numerals;
	let new command be text;
	repeat with testnum running from start to end:
		now new command is "test [prefix][testnum]. [new command]";
	change the text of the player's command to new command.

On the plus side, it’s I7 only. On the minus side, the maximum size of the command buffer imposes a limit of 12 tests at a time.

I thought it was interesting that there doesn’t seem to be a built-in phrase for converting texts to numbers.

1 Like

There sort of is: you can test whether something matches the topic “[number]”, which stores the result into “the number understood”.

But of course this clobbers a global variable, and you can’t compare texts against topics directly, so what you have to do is save the player’s command and the number understood into temporaries, alter the player’s command to the text in question, compare it against the topic, mark down the number understood, and restore the globals.

At least a few extensions have provided phrases to expedite all of this.

1 Like

Yes, but in this case the numbers of interest are only pieces of a “word” from a topic’s standpoint. (Not even punctuated words will separate out an alphabetic prefix, if present.)

I also found it surprising that this was the first time I had ever missed having a phrase like that.

1 Like

Right, that’s why you have to do the whole rigamarole with changing the player’s command: extract the number from the surrounding characters using text manipulation, change the player’s command to the number, parse it with topic-matching, then change the command back. If it didn’t have the letter in front you could just do matching on that snippet, but alas.

1 Like