Bubbling Beaker Awards (Award #23, Apr 19 2024)

As CrocMiam pointed out on the thread from 16 months ago, it’s also possible to approximate ternary relations via a table. But as Dannii pointed out in response, the main drawback of a table is that it has to be sized at compile time and cannot be expanded at run time, so the author must attend to this detail. By contrast, with Zed’s technique, the various relations of colors to colors will all be sized correctly by the compiler.

With a table approach, the author can set up initial relations which will be compiled into the starting state of the table. With Zed’s approach, the author must write a rule to initialize the relations at startup, because all of the anonymous relations begin empty. (It’s about the same amount of typing either way.)

With either approach, the state of pseudo-ternary relations can be modified at run time.

With a table approach, the binding between elements of the tuple is via sharing a row in the table. With Zed’s approach, binding is in two stages: A given second color is bound to the mix result color via a relation assertion, and the first color is bound to a set of relation assertions via the link between that color value and its relation-holding property.

With either approach, phrases must be used to both set and get information from the pseudo-relation.

Overall, the two approaches seem comparable:

Comparison of methods
Color is a kind of value.

The colors are brown, red, orange, yellow, blue, green, indigo, violet.

[Zed's technique]

A color has a relation of a color to a color called the mixmaster.

To (c1 - color) mixed with (c2 - color) is (c3 - color):
  now the mixmaster of c1 relates c2 to c3;
  now the mixmaster of c2 relates c1 to c3;

[if no relation, decides on default value of color]
To decide what color is (c1 - color) mixed with (c2 - color):
  if c2 relates to a color by the mixmaster of c1, decide on the color that c2 relates to by the mixmaster of c1;

When play begins (this is the initialize color mixing relations rule):
	[these lines cause crash for Glulx in 6M62]
	red mixed with blue is violet;
	red mixed with orange is red;

[Table-based implementation]

Table of Color Mixing
color1 (color)	color2 (color)	result (color)
red	blue	violet
red	orange	red
with 50 blank rows.

[if no row, decides on default value of color]
To decide which color is the result of mixing (c1 - color) with (c2 - color):
	repeat through the Table of Color Mixing:
		if color1 entry is c1 and color2 entry is c2, decide on result entry;
		if color1 entry is c2 and color2 entry is c1, decide on result entry;


When play begins (this is the compare techniques rule):
	say "red + blue = [red mixed with blue].";
	say "red & blue = [result of mixing red with blue].";
	say "blue + red = [blue mixed with red].";
	say "blue & red = [result of mixing blue with red].";
	say "red + orange = [red mixed with orange].";
	say "red & orange = [result of mixing red with orange].";
	say "orange + green = [orange mixed with green].";
	say "orange & green = [result of mixing orange with green].";

… but Zed’s is definitely the “mad science” version.

4 Likes

In celebration of International LISP Day (well, not really- that’s May 4th) - today everything’s a list!

Here’s a method to do the same thing with a list of lists of colours…

Which enables the 3-tuples, e.g. {red,yellow,orange}
(i) to be asserted in initial setup without an initialisation routine (unlike a relation property)
(ii) to be modified at run-time
(iii) to be added to indefinitely at run-time subject to the number of colour-combinations available (unlike a table)

This heavily commented example sets up an intial list of tuples, including some inappropriate duplicates, then manipulates the list through user input.

1 Like

Musing a little more about the different applications of these methods:
(i) Relations allow some nice built-in phrases such as ‘list of colours to which yellow relates by the mixmaster of blue’
(ii) Tables allow the widest range of different kinds to make up the tuple from (three columns of different kinds), while if using a list of lists all the elements of the tuple must be of exactly the same kind. It would be possible, if a little awkward, to maintain three corresponding lists of different kinds where a tuple is made up of entry x of list 1, entry x of list 2 and entry x of list 3.

1 Like

This week’s prize, Bubbling Beaker Award® #6 is presented to @BadParser, who is not a mad scientist but seems to take a scientific approach to Inform. BadParser’s award-winning post demonstrates how to skirt the I7 compiler’s refusal to accept an action that applies to two kinds of value (and zero objects) by deploying a general parsing routine modeled after the “third noun” example from the DM4.

BadParser, congratulations! You are invited to share details about inspiration, conception and execution here, if so inclined.

6 Likes

I’ll note that you’re a mad scientist if you say you’re a mad scientist, so maybe BP is a :test_tube:mad scientist :atom_symbol: and they just haven’t told us yet!

4 Likes

I am truly humbled and not sure how worthy this post is for such an honor, but would like to respond by saying: my inspiration was @raragi’s question (in post #6 of that original thread); my conception was to search this forum; my execution was to slightly modify the code I found.

However, to make my contribution a little more “Beaker-worthy” and, in the spirit of fellowship with past award winners, I have decided to extend the code and combine it with last week’s winner, Zed Lopez’s BBA #5.

This is also an attempt to finally answer Daniel’s question to @zarf from December, 2013:

This implementation is not really complete as a general solution, which is left up to the reader. I may not be a mad scientist, but I do like to drive scientists mad.


"Action with Two Values - Bubbling Beaker Awards Edition" by Mike Tarbert

[NB: does not compile at all in version 6M62; crashes as written here in 10.1.0 (in Borogove app); works as written in 10.1.2. In order to work properly in general using 10.1.2 (or at all in Borogove's 10.1.0) requires voodoo and/or other black magic unbeknownst to this author (or just trace experimentation) to determine the second argument for ParseToken called within SecondColor.]

Lab is a room. 

Section 1 - Zed Lopez's Bubbling Beaker Award Winner #5

color is a kind of value.

the colors are brown, red, orange, yellow, blue, green, indigo, violet.

a color has a relation of a color to a color called the mixmaster.

to (c1 - color) mixed with (c2 - color) is (c3 - color):
  now the mixmaster of c1 relates c2 to c3;
  now the mixmaster of c2 relates c1 to c3;

to decide what color is (c1 - color) mixed with (c2 - color):
  if c2 relates to a color by the mixmaster of c1, decide on the color that c2 relates to by the mixmaster of c1;

when play begins (this is the set up colors still on the TODO list rule):
	red mixed with blue is violet;
	red mixed with yellow is orange;
	yellow mixed with blue is green;
	

Section 2 - BP's BBAW #6 Modified for Use with a New KOV

Include (-
Global second_color;
-).

The second color understood is a color that varies.
The second color understood variable translates into I6 as "second_color". [represented as a number in i6]

Include(-
[ SecondColor x firstnum;
	firstnum = parsed_number; ! saves the first parsed number locally
	x = ParseToken(GPR_TT, 212542); ! <- I don't know how to calculate this GPR number
	if (x == GPR_FAIL or GPR_REPARSE) return x;
	second_color = parsed_number; ! saves the second parsed number into a global
	parsed_number = firstnum; ! returns the first number to its usual global spot
	return GPR_PREPOSITION;
];
-).

The Understand token second-color translates into I6 as "SecondColor".

Section 3 - Mixing It Up

Mixing it with is an action applying to one color.

Understand "mix [color] with [second-color]" as mixing it with.

Report mixing it with:
	say "Mixing [color understood] with [second color understood] (in this implementation at least) yields: [color understood mixed with second color understood].".
	
test me with "mix red with blue / mix blue with red / mix blue with yellow / mix violet with yellow / mix blue with blue / mix blue with vermilion".

I would also like to thank the academy and to tell all you youngsters out there that you never know what a little pouting in the Lounge can get you.

3 Likes

I don’t know how to find this number programmatically (it is the address of the General Parsing Routine (GPR) represented by the [color] token, and as far as I know, inaccessible to I7) but by creating a replica GPR function, we can use the name of that as the argument instead, which is a litlle less ‘voodoo’ than finding and inserting a highly-unstable magic number:

1 Like

Here is a method to find the address of the GPR routine represented by a token such as [color], by pulling it from the grammar of a verb word that in the first grammar line in its grammar table includes [color] as the first token after the verb word.

In this example, after Understand "mix [color] with [second-color]" as mixing it with., the grammar token [color] is indeed the first token after ‘mix’ in a single grammar line, so…

First when play begins: set color token.

To set color token: (- default_color_token=GetGPRRoutine('mix'); -).

Include (- 
Global default_color_token;
[ GetGPRRoutine verb_wd i line_address first_ttype first_tdata pcount;
! call this with a verb (dictionary word) and if the 1st token of the 1st grammar line for that verb is a GPR routine, the function returns its address

	i = DictionaryWordToVerbNum(verb_wd);

	line_address = ((#grammar_table)-->(i+1))+4;     ! look up start of grammar table for this verb, skip 1 byte for <no of grammar lines>; 3 for <##action name + flags byte> of first line, to reach first token
	first_ttype=line_address->0 & $0F;                         ! token type (we want GPR_TT)
	first_tdata=(line_address+1)-->0;                           ! token data
	
	if (first_ttype==GPR_TT) return first_tdata;    ! first_tdata is address of the GPR routine
	print "###Error - GetGPRRoutine() tried to find a GPR routine address from the first grammar token of the verb'", (address) verb_wd, "' but that token wasn't a GPR routine.^^";
!	PrintGrammar(verb_wd);
	rfalse;
];
-).

which means when play begins, the I6 global default_color_token now contains the address of the GPR routine represented by [color], and we can use that in our second colour token routine, thus:

[ second_color_token x firstnum;
	firstnum = parsed_number; ! saves the first parsed number locally
	x = ParseToken(GPR_TT, default_color_token); ! <- global assigned when play begins with the address of the [color] token GPR routine
	if (x == GPR_FAIL or GPR_REPARSE) return x;
	second_color = parsed_number; ! saves the second parsed number into a global
	parsed_number = firstnum; ! returns the first number to its usual global spot
	return GPR_PREPOSITION;
];
5 Likes

Addendum:
Although it’s possible to modify this code to extract the address of a GPR token routine that is not the first in a grammar line, or is found in a grammar line that is not the first line in a verb’s grammar table (in cases where the verb has more than one grammar line), it’s simpler to stick to the code as written and if desired or needed, invent a dummy verb/action declared with just one line of suitable grammar to extract the [color] token address from:

Color-extracting is an action applying to one color.
Understand "c-ext [color]" as color-extracting.

To set color token: (- default_color_token=GetGPRRoutine('c-ext'); -).

If you want to see the grammar table for a verb, the little-known debugging command showverb c-ext, for example, will do so:

>showverb take
Verb ‘carry’ ‘hold’ ‘take’
* ‘inventory’ → Inv
* multi → Take
* ‘off’ noun → Disrobe
* noun ‘off’ → Disrobe
* multiinside ‘from’ noun → Remove
* multiinside ‘off’ noun → Remove

>showverb c-ext
Verb ‘c-ext’
* Routine(212794) → A_color_extracting

Here, * stands in for <(one of) the command word(s)>, so for example
* inventory matches ‘take inventory’, invoking the ##Inv action;
* multiinside 'from' noun matches ‘take <one or more things inside the something mentioned later> from <something>’, invoking the ##Remove action;

A_color_extracting is the internal I6 name that the compiler has invented for our new dummy action, which could be elsewhere referred to in I6 code as ##A_color_extracting.

212794 is the address of the [color] GPR routine for this particular compilation (which the previously-posted code extracts using GetGPRRoutine(‘c-ext’) ), and corresponds to the ‘magic number’ found here x = ParseToken(GPR_TT, 212542) in the SecondColor token routine of @BadParser 's original post. As demonstrated here, that ‘magic number’ is likely to change with changes to the source code and/or compiler, so can’t be relied on over time. Hence the usefulness of a more general solution as presented here and previously.

NB study of the grammar for take illustrates some interesting points, such that one can take inventory by typing ‘carry inventory’ or remove clothing (the disrobe action) by typing, for example, ‘hold coat off’.

3 Likes

This week’s prize, Bubbling Beaker Award® #7 is presented to @drpeterbatesuk, who is the kind of quiet, diligent mad scientist who not only builds fabulous doomsday devices but also releases high-quality spec sheets and do-it-yourself guides for the general public. His award-winning post demonstrates how to smuggle I6 code deemed illegal in Inform 10.1.2 past the compiler’s inspection for contraband instructions. (Think of it as a “jailbreak” for 10.1.2.)

By virtue of this invention’s particular combination of deviousness and utility, the award is also granted the distinction of being Peer Revued™.

Congratulations, drpeterbatesuk! This marks your second award. I look forward to the discussion!

9 Likes

I am most humbled by this unexpected Peer Revued™ accolade.

I will submit further context for and useful examples of this ‘discovery’ over the weekend, assuming the agents of sensible and non-deprecated coding don’t reach me first…

4 Likes

Well, my beaker has been bubbling furiously over the past several days and I am now ready to share new discoveries with the world.

First a little context. I had been aware from past experimentation that (apparently uniquely in Ver 10) I6 code included through the Include (- ... -) when defining <kind-name>/<function-name> syntax was pasted verbatim into ‘auto.inf’ without passing through the I6->Inter->I6 compilation chain. Chunks of I6 code pasted verbatim in this way are called ‘splats’.

Until a fortnight ago, I hadn’t really seen much use for this discovery, particularly since it seemed to enable only splats that would be part of a kind or object property.

I was inspired to think again about this while grappling with the iniquities of Glulxe’s random number generator (see this thread), which in turn led to a search for ways to replace Inform 6’s built-in random() function.

This is straightforward in Version 9.3/6M62, using the I6 Replace directive.

Unfortunately, The Replace directive can’t be used in Version 10 I6 inclusions, and the Include (- ... -) replacing ... syntax also doesn’t work for inbuilt I6 functions like random(). What was needed was some other way to smuggle a Replace directive into ‘auto.inf’. After a few false leads and dead ends, I had my ‘Eureka!’ moment.

There was a way after all to use Include (- ... -) when defining <kind-name>/<function-name> to put splats into ‘auto.inf’ outside kind and object properties.

The devious trick required is to start the splat with a semicolon, which abruptly but legally terminates the in train kind/object declaration; then have some useful I6 code which is now outside any kind/object declaration; then finish the splat off with a new dummy kind/object declaration header that absorbs the orphaned ‘tail’ of the original declaration.

We end up with two slightly stunted kind/object declarations with our injected I6 code inbetween.

I must admit to punching the air when I tried this and it worked :slightly_smiling_face:

e.g.

Class K17_dummy_kind
  class K0_kind
    with plural bc_U204
    with short_name bc_U205
    with article bc_U206
    with list_together bc_U207
;

becomes something like

Class K17_dummy_kind
  class K0_kind
 ;                         ! <- NB semicolon here to terminate the
                           ! dummy_kind Class declaration header

! ######## vvvvv INSERT I6 CODE BELOW HERE vvvvv ##############

Replace random;            ! replace an inbuilt system function with our own,
                           ! declared hereafter

[random; return 1234; ];   ! a not-very-random replacement random number generator :)

! ######## ^^^^^ INSERT I6 CODE ABOVE HERE ^^^^^ ###############
	
Class dummy_kind_2         ! declare a 2nd dummy kind class header 
  class K0_kind            ! <- insertion ends here
    with plural bc_U204
    with short_name bc_U205
    with article bc_U206
    with list_together bc_U207
;

I append in the next post some detailed deliberations and demonstrations of some rather more obscure uses for this technique in a heavily-commented example I7 project.

EDIT: split across 2 posts due to exceeding the character limit

EDIT2: by far the best way to read this is to copy and paste both posts sequentially into the IDE, compile as Current Version for Glulx, then read the code alongside the test output. EDIT: You may need to scroll up the IDE output window to see the test output after compilation is complete

EDIT3: a further more forceful nudge: don’t attempt to read the following code as-is on the forum website. It’ll be slightly mangled, misformatted and as a result have some words missing, crossed out etc. Copy and paste it into the IDE (or a basic text editor of your choice)

2 Likes

don’t attempt to read the following code as-is on the forum website. It’ll be slightly mangled, misformatted and as a result have some words missing, crossed out etc. Copy and paste it into the IDE (or a basic text editor of your choice)

Part 1 - open this up then copy & paste into a blank project in the IDE. (click on the little copy icon that appears top-right of the window when you 'hover' the mouse pointer over the opened-up text.)
"Scratchpad_Include_when_defining" by PB

Part - Preface

Chapter - A Warning

[                                                                 ################### STOP! ##################

									facilis descensus Averno;
									nocte atque dies patet atri ianua Ditis;
									sed revocare gradum superasque evadere ad auras,
									hoc opus, hic labor est.
														Virgil, Aeneid vi. 126-29


								  "The descent to the Underworld is easy:
								    through day and night the door of black Dis lies open.
								    But to retrace your steps and escape to the upper air -
								    there is trouble, there is toil."
								
                                                                ################### STOP! ##################
]								
[
Do not proceed. You really do not want to do this. No, honestly, you really really do not want to do this. Oh, alright, if you must, but don't say
I didn't warn you when it all goes horribly wrong...

All that follows is highly deprecated and is certified to work no further than the current release of Inform 7 (10.1.2)

It mostly relates to changes introduced with version 10, so is largely not needed if you are working with 9.3/6M62.

If you think you might need any of these techniques, consider first whether to simply revert to 9.3/6M62 for your project.

But if you would be with Dante of one equal temper of heroic hearts, fellow traveller, read on...


]

Chapter - A Discursion

[
									Two roads diverged in a yellow wood, and I
									I took the one less traveled-by-
									and that has made all the difference.
									
															Robert Frost
]

[

Inform 7 is built upon the foundation of Inform 6, a very different, rather austere, C-like language. Inform 6 and its predecessors date back to the early 1990s.  Inform 7 was a major break from this tradition, but everything written in Inform 7 (I7) source code is compiled (converted) to Inform 6 (I6) code before that I6 code is itself compiled to a story file that can be run on an interpreter.

The intermediate I6 code produced by the I7 compiler can be found as a text file in the Build directory of the <project-name>.inform folder and is called 'auto.inf'.  'auto.inf' is (re)created every time [Go] is clicked in the IDE, assuming the I7->I6 compilation process succeeds.

Prior to Inform version 10, this was the only stage of I7->I6 compilation.  I7 source code was compiled to I6, the I6 code in the extensive template files defining the Library and the Standard Rules were directly pasted in alongside, and any I6 inclusions in the I7 source were also simply pasted into the 'auto.inf' file at the place directed by the Include (- ... -) directive.

All this changed with Version 10.  Now there is yet another intermediate language called Inter.  I7 source is compiled in the first instance to Inter. I6 inclusions in I7 source are also compiled in the first instance to Inter. What used to be the I6 template files are pre-compiled to Inter, in files called kits.
All these sources of Inter- I7 source, I6 inclusions, kit files, are then combined together. Unlike the case with I6, however, there is no persistent file holding the combined Inter code equivalent to 'auto.inf'.  The combined Inter code is then compiled again to I6, finally producing the I6 'auto.inf' file.

It can be seen from this description that in the I7 context, code initially written in I6, whether template code or an inclusion, ordinarily always passes through Inter before becoming I6 once more.  Sometimes the I6->Inter->I6 compilation chain leaves the resulting I6 pretty much unchanged, but sometimes it looks very different.

It is also clear that there are two different compilers that compile I6 code to something else:
	(i) the I6->Inter compiler used 
		(a) to compile I6 template code to Inter kit files
		(b) I6 inclusions in I7 code to Inter
	(ii) the I6->story file compiler used to compile to a Z-machine or Glulx story file
	
The second of these is essentially the same compiler developed over the past 30 years to compile stories written entirely in Inform 6.
The first is a new compiler developed for Inform 7 Version 10 in order to produce Inter code.

In an ideal world, the Inform 6 language recognised and compiled by these two compilers would be identical, but that's not the case.  Firstly, some of the more obscure syntaxes of the Inform 6 language are not supported by the new I6->Inter compiler. Secondly, like any new extensive and complex program, there are a small number of bugs in the new compiler, such that some I6 code that should work, and does work with the old compiler, doesn't work with the new.

In addition to this, some things written in I6 code, whether in template files or inclusions, doesn't make it through the I6->Inter->I6 compilation chain. Most obviously, comments are stripped out.  More subtly, conditional compilation directives such as '#ifdef <conditionally compiled code> #endif are not passed through the compilation chain.  They are instead considered at the time of I6->Inter compilation, and the decision is taken to compile <conditionally compiled code> or not at that point.  This has led to some bugs even in the kit files, where code that needed to be compiled has not been. It also means that there is no mechanism for template files or I6 inclusions to put conditionally-compiled code segments into the final I6 file, to be compiled or not according to the circumstances when it is compiled from I6 to a story file.

A further complication is that  a number of compiler-generated functions and data structures are only created in the final stages of the Inter->I6 compilation process. They therefore do not yet exist at the time of the I6->Inter compilation. Attempts to reference these functions and structures in I6 templates or inclusions will cause a failure of the I6->Inter compilation due to efforts to reference a not-yet-existent data structure or function. In addition, these data structures and functions cannot be replaced using the Include (- ... -) replacing <function-name> syntax, and the I6 Replace directive cannot be used in template files or I6 inclusions.

There are two other kinds of function that can cause difficulty.  Built-in functions are part of the I6 language itself, common examples being random() or child() or the printing functions like name(). These are known to the I6->Inter compiler and so can be used in I6 templates or inclusions.  However, should one wish to replace a system functions with a revised or enhanced version, the Include (- ... -) replacing <function-name> syntax does not work for system functions and, as previously noted, the Replace directive cannot be used in template files or I6 inclusions.  Veneer functions are a handful of 'operating system' support functions that are not part of the I6 language but are compiled by the I6->story file compiler into the final story file.  They are unknown to the I6->Inter compiler, and therefore unavailable to I6 templates or inclusions, but they can be called by I6 in the final 'auto.inf' file.

The developers of Inform 7 do not wish to encourage 'hybrid' coding in a combination of I7 and I6 code (see Writing with Inform §27.14. Using Inform 6 within Inform 7). An early goal of Ver 10 development was to progressively eliminate sections of I6 code that needed to be pasted verbatim into the final I6 source file, as what were called 'splats', without first passing through the I6->Inter->I6 compilation process.  This endeavour was largely successful, as the I6->Inter->I6 compilers steadily improved.  The facility was retained for authors to Include short sections of their own I6 code, although the practice is gently discouraged. These inclusions do however need to pass through Inter before reaching their final I6 form, introducing the restrictions alluded to previously.

However, one facility remains to inject 'splats' directly into the final I6 file without I6->Inter->I6 compilation-

Include (- ... -) when defining a/-- <kind-name>/<object-name>.

(See Writing with Inform '§27.21. Inform 6 objects and classes', which concludes with this portentious admonition: WARNING: The "Include (- ... -) when defining ..." usage still works for the moment (except in projects compiled to C at the command line, where it may fail), but it is deprecated and likely to be removed in later versions of Inform. Avoid it if at all possible.)

As things stand, the I6 code represented in the phrase above by ... is pasted directly as a 'splat' in the midst of the final I6 declaration of the kind or object, and this provides a 'back-door' or 'bridge' to do some of the things with I6 that became no longer possible in Ver 10 of I7.

This Inform 7 project illustrates some of the things you can do only with 'splats', and methods to achieve them. Will you need them? Almost certainly not. But just in case, or if you're simply curious, read on...

]


Part - Setup

Chapter - Geography

[
							To me, it seems a dreadful indignity to have
							a soul controlled by geography.

												George Santayana
]

The Last Homely House is a room.
The Lonely Mountain is a room. North is the Withered Heath.

Chapter - Objects

[
									I have a lot of objects in my space,
									little things, reminders, memories.

													Marc Newson
]

A dragon is a kind of animal. Smaug is a dragon. Fafnir is a dragon. The Smaug object translates into I6 as "Smaug". The Fafnir object translates into I6 as "Fafnir".

A person has a room called lair. The lair of a person is usually The Last Homely House. The lair of an animal is usually the Withered Heath. The lair of Smaug is The Lonely Mountain.

A person has a number called hit-points. The hit-points of a person is usually 8.  The hit-points of an animal is usually 12.  The hit-points of a dragon is usually 88.  The hit-points of Smaug is 120.

Part - Include ___ when defining ___

Chapter - Credo

[
								"We choose ... to do (these)... things, not because they are easy,
								 but because they are hard;
												
													John F. Kennedy  speech Sep 12 1962
													
								"We choose to do these things not because they are easy,
								 but because we thought they were going to be easy.
								
																Programmer's Credo
																
]

Chapter - Including a 'splat' when defining a kind



Section - Declaring a dummy-kind to include a 'splat'

A dummy-kind is a kind of object. 

Include (- ;   !   <- NB semicolon  here to prematurely terminate the dummy_kind Class declaration header

! ############################### vvvvv INSERT I6 CODE BELOW HERE vvvvv #################################

!                                                                                ------ Replacing System Functions -----

! System functions are a dozen or so functions built into the I6 language that are automatically compiled to be called by I6 code
! Common examples are random(); objectloop(); child(); etc.
! As noted above, they can be replaced by Replace directives placed here:

Replace random;                ! replace an inbuilt system function with our own, declared hereafter

[random; return 1234; ];     ! a not-very-random replacement random number generator :)

!                                                            ------ Functions using I6 syntax not supported by the I6->Inter compiler -----

! developing Inform 7 involved the monumental task of reimplementing the I6 compiler- with a source code file for a new 'intermediate'
! language, called 'Inter' as the compilation target rather than a finalised Z-machine or Glulx story file.
! Both Inform 7 source code, and Inform 6 source code (in source for kits, or inclusions within Inform 7 source files) are compiled to Inter.
! The combined Inter file is then (usually) compiled to the unified Inform 6 source file represented by 'auto.inf'.
! I6 in Inform 7 inclusions is therefore subjected to the following compilation chain:
!	I6 inclusion in I7 source -> Inter -> I6 in 'auto.inf' -> final story file e.g. 'output.ulx'
! The compiler compiling I6 inclusions to Inter is completely different to the one compiling the I6 in 'auto.inf' to 'output.ulx'
! The compiler compiling the I6 in 'auto.inf' to 'output.ulx' is essentially the same one you would use to compile stories
! which you had authored in Inform 6 code from the start.
! Mostly the two compilers recognise the same I6 code in the same way, but there are a few subtle differences,
! largely consisting of things you can do using the 'auto.inf' to 'output.ulx' compiler but not the I6 inclusion -> Inter compiler.
! some of these are documented in Writing with Inform, in the I7 documentation '§27.19. Longer extracts of Inform 6 code'
! some more are mentioned in posts on the Interactive Fiction Community Forum https://intfiction.org/c/authoring/inform-7/
! and others in submissions to the Inform 7 bug tracker https://inform7.atlassian.net/jira/software/c/projects/I7/issues
! one useful feature of switch statements in 'canonical' I6 is missing from the I6->Inter compiler:
! the ability to have cases expressed as a range of values between two constants, such as 13 to 55 or  'a' to 'z'
! the latter otherwise has to be laboriously expressed as a list of 26 constants:  'a', 'b', 'c', ... 'x', 'y', 'z'

[ ZSCII_range x;        ! illustrating a switch statement using the 'to' syntax disallowed by the I6->Inter compiler.
	switch (x) {
		8: print "delete";
		9: print "tab";
		11: print "em";
		13: print "newline";
		32: print "space";
		33 to 47, 58 to 64, 91 to 96, 123 to 126: print "punctuation/symbol";
		48 to 57: print "numeral";
		48 to 57: print "numeral";
		'A' to 'Z' : print "uppercase";
		'a' to 'z' : print "lowercase";
		129 to 131 : print "cursor key";
		132 to 144 : print "function key";
		145 to 154 : print "numeric keypad";
		155 to 223 : print "international";
		252 to 254 : print "mouse";
		default: print "undefined-",x;
	}
];

Array ZSCIIstring string '?' 'A' 'c' '@~N';     ! '@~N' chosen as example as it is the only international character with the same value (209) in
                                                                    ! ZSCII and Unicode, so displays the same whether compiling to Z-machine or Glulx

[ MyFunction x;             ! illustrating a switch statement using the 'to' syntax disallowed by the I6->Inter compiler.
	switch (x) {
		1: print "was destroyed in a fire on the launchpad";
		2 to 3: print "was cancelled after the failure of Apollo 1";
		4 to 6: print "was a successful test launch of the Saturn 5 rocket";
		7, 9: print "completed a test-flight in Earth orbit";
		8, 10: print "completed a test-flight in lunar orbit";
		11, 12, 14 to 17: print "made a landing on the Moon";
		13: print "made it back safely after a catastrophic explosion";
		18, 19: print "was cancelled due to budgetary cuts";
		20: print "was cancelled to free up a heavy launcher for the Skylab station";
	}
];

!                                                                                         ------ Veneer Functions -----

!  The veneer is a short 'invisible' segment of I6 compiled into the final story file along with the ordinary 'visible' I6 source code.
!  It provides a few dozen basic 'operating system' functions that can be called by I6 code or system functions
!  Any of these that are duplicated by 'visible' I6 source code are automatically overridden and replaced by that code
!  so [ Print__PName; print "Fooled you!";]; would automatically replace the veneer function to print the name of a property
!  The Inform Library overrides many of the veneer functions, but a few remain, as in this example which prints
!  the I6 textual name associated with a given property id number.
!  See the Inform 6 Technical Manual for more information about the veneer:
!  https://www.ifarchive.org/if-archive/infocom/compilers/inform6/manuals/technical_manual.txt
!  Veneer functions are not usually 'visible' to the I7/I6->Inter->I6 compilers, so normally can' t be referenced in I6 inclusions
!  but they can be referenced within a 'splat':


[ PrintVeneerDescription p;						  
	Print__PName(p);                                                       ! here we call a veneer function using the parameter passed to the I6 function
];                                                                                         ! p should be an I6 property id number- not an I7 property, which is a reference to a
                                                                                           ! word array of metadata relating to the property. The I6 property id number is
                                                                                           ! given by (I7_property-->1)

!                     ------ Conditional Compilation ------   ------Message Directive ------  ------ Array Directive ------


#Ifdef TARGET_ZCODE;							! example of conditional compilation applied to the final I6 source
	Message "Compiling greeting for Z-machine....";    ! information about the compilation (will appear in IDE Console of [Results] tab)
	Array Greeting string "Welcome to Z-code!";          ! "double-quoted" Array declarations are not allowed by I6->Inter
#Ifnot;										     ! nor is the 'string' array declaration subtype
	Message "Compiling greeting for Glulx...."; 
	Array Greeting string "Welcome to Glulx!";
#Endif;

!                                                                                         ------ Verb Directive -----

                                  ! the Verb directive is recognised in normal I6 inclusions but it causes a compiler failure when loading WorldModelKit

Verb 'tabulate'			        	! this simply allows the command 'tabulate inventory' to evoke the inventory action
	* 'inventory' -> Inv ;                ! it is the equivalent of the I7 'Understand "tabulate inventory" as taking inventory.'
	                                                    
[ XyzzySub; "Expectantly, you cry 'xyzzy!'... but nothing happens.^"; ];					
Verb ’xyzzy’
	* -> Xyzzy;                               ! here we create a whole new action and grammar to invoke it
                                                         !  equivalent to 'Xyzzying is an action applying to nothing. Understand "xyzzy" as xyzzying.
	                                                 !  Carry out xyzzying: say "Expectantly, you cry [']xyzzy![']... but nothing happens.".
	                                                 !  The xyzzying action translates into Inter as "Xyzzy". '
	
							! Unfortunately, because this I6 code appears before all the kit-and-I7-generated Verb directives,
						        ! we cannot use the Extend directive here, which will fail the I6 compilation with 'no previous grammar'.
						
						        ! There is little that can be done using the Verb directive that isn't possible and better to do in Inform 7.
						        ! It does provide complete authorial control over the sequence in which grammar lines appear in a verb's
						        ! grammar table, and therefore the order in which they are considered by the parser.
						        ! (This sequence is ordinarily controlled, according to a rather arcane set of rules, by the I7 compiler.)
						        ! Very occasionally, when the compiler's sequencing is unhelpful, direct definition of a verb's grammar
						        ! table with the Verb directive may be of use.


!                                                                                         ------ Superclass Operator -----

! The superclass operator in I6 is a somewhat obscure syntax invoking the property an object would
! have otherwise inherited from a class it belongs to, if it had not had its own property specifically defined.
! To quote from the Inform Designer's Manual 4 (p.64):

! ---------------------------------------------------------------------------------------------------------------------

! It fairly often happens that an instance of a class needs to behave almost, but not
! quite, as the class would suggest. For instance, suppose the following Treasure class:
	
!     Class Treasure
!     with deposit [;
!         if (self provides deposit_points)
!             score = score + self.deposit_points;
!         else score = score + 5;
!         move self to trophy_case;
!         "You feel a sense of increased esteem and worth.";
!     ];

! and we want to create an instance called Bat_Idol which flutters away, resisting
! deposition, but only if the room is dark:
	
! 	Treasure Bat_Idol "jewelled bat idol"
!	 with deposit [;
!	     if (location == thedark) {
! 	        remove self;
! 	        "There is a clinking, fluttering sound!";
! 	    }
! 	    ...
! 	];

! In place of ..., what we want is all of the previous source code about depositing
! treasures. We could just copy it out again, but a much neater trick is to write:
	
! self.Treasure::deposit();

! Instead of sending the message deposit, we send the message Treasure::deposit,
! which means ‘‘what deposit would do if it used the value defined by Treasure’’. The
! double-colon :: is called the ‘‘superclass operator’’. (The word ‘‘superclass’’, in this
! context, is borrowed from the Smalltalk-80 language.)

! object.class::property is the value of property which the given object would
! normally inherit from the given class. (Or it gives an error if the class doesn’t provide
! that property or if the object isn’t a member of that class).

! It’s perfectly legal to write something like x = Treasure::deposit; and then to
! send Bat_Idol.x();.

! ---------------------------------------------------------------------------------------------------------------------

! I7 rebrands classes as kinds, such that 'Treasure is a kind of object.' will compile to an I6 directive similar to 'Class Treasure'.
! The superclass operator is useful in I6 predominantly because many I6 properties are elaborate routines which it might be inefficient and
! potentially poor coding practice to copy-and-paste within the specific properties of multiple objects of a class.
! In Inform 7, elaborate routines are nearly always in To... phrases or rulebooks, not an object's properties, so the inability to access
! the base properties of an object's kind is not generally missed.

! It isn't however possible to write  for example 'say the printed plural name of a person'-
! Inform asks for a particular person to be specified- but by default the printed plural name of every person is "people",  so if we want to
! know what is defined as the default printed plural name of the person kind we can usually write 'say the printed plural name of the player'
! But if we have "a dragon is a kind of animal", a dragon is a person too, and if our player has been declared as a man (printed plural name 'men')
! there may be no specific object with a printed plural name of "people", if we want to know what the default printed plural name of a
! dragon-as-a-person would be- "people" or "persons"?- we can use the superclass operator like so <dragon-name>.person::plural

! this turns out to be more awkward that one might think, because
! (i) we need to pass a property from I7, and there is a bug in Inform Ver 10+ that prevents property parameters being passed to I7 phrases:
!      fortunately we can work around this because property parameters can still be passed from I7 to I6 inclusions
! (ii) there seems to be no obvious way to pass a kind from I7 as a parameter to either an I6 inclusion or an I7 phrase:
!	we work around this by using a text parameter representing the kind name to look up the I6 representation of the kind
!       and passing that- an I6 memory address- to I6 as a number parameter
!   
!  having finally assembled our property and kind parameters, we can pass those with our object to I6 and find <object>.<kind>::<property>
!       
!  see the example below where a common property is used to access the plural printed name property of a kind that an object inherits from


! ############################### ^^^^^ INSERT I6 CODE ABOVE HERE ^^^^^ #################################
	
Class dummy_kind_2        ! declare a 2nd dummy kind class header to absorb the orphaned remainder of the compiler's dummy-kind declaration
	class K0_kind            ! which will follow on after this in the final compiled I6 source file (auto.inf)
					 ! Because the I7/I6->Inter->I6 compilers don't know about this 2nd dummy kind, although it will be compiled
					  ! into the final story file it will not be accompanied by all the structures that the I7/I6->Inter->I6 compilers
					  ! normally compile to accompany a kind declaration, e.g. it will not appear in the kind hierarchy array
					
-) when defining a dummy-kind.

2 Likes

don’t attempt to read the following code as-is on the forum website. It’ll be slightly mangled, misformatted and as a result have some words missing, crossed out etc. Copy and paste it into the IDE (or a basic text editor of your choice)

Part 2 - open this up then copy & paste into IDE at end of Part 1 (blank line between)

Section - Comments on replacing functions using an I6 Replace directive in a 'splat'

[

The Replace directive has two forms-

Replace <function-name>;
	The directive must appear in the I6 source before the first declaration of <function-name>, or in the case of an inbuilt function like random(),
	before the first reference to it in the source.
	It may be followed by any number of declarations of <function-name>.
	The compiler compiles only the *last* declaration of <function-name> in the source, discarding the rest.
	In terms of ordering, declarations in files marked as system_file, or inbuilt functions from the veneer, come *before* those in normal source files,
	so the last declaration in a normal source file (such as auto.inf) has highest priority.
Replace <function-name> <new-function-name>;
	The directive must appear in the source before the first declaration of <function-name>
	The compiler compiles the *first* declaration of <function-name> in the source as <new-function-name> instead, effectively renaming it.
	This allows a kit function to be renamed rather than discarded, perhaps to be called by its replacement to implement default behaviour.
	It is also possible to rename functions created and inserted directly into the I6 by the compiler, although this is unlikely to be useful
	unless a means is found to inject a replacement function with the original name later in the source.
	Inbuilt I6 functions like random() can't be renamed in this way, however.
	
These two definitions of Replace in combination mean that when two like-named declarations occur in an I6 source file, it's not possible to retain the earlier one unchanged- the earlier one must either be not compiled at all, or renamed.

This means that it's impossible to use Replace to replace functions generated by the Inter->I6 compiler, because these will be inserted into the final I6 source file later than splats from 'while defining....', or compiled functions from Include (- <function> -) phrases.

Unfortunately, in Ver 10+ the author has lost the ability to control where I6 is inserted or included in the final I6 source file.

This is a particular problem because the approved syntax of 'Include (- [<function-name>...]; -) replacing "<function-name>".' will only replace functions directly declared in kits, not inbuilt functions like random() or functions created entirely by the Inter->I6 compiler.

All this means that this despite this hack there is still no means to replace functions created entirely by the Inter->I6 compiler, although inbuilt functions can be replaced through the 'when defining...' method.

It's not possible to finesse the situation by combining multiple Replace statements, as the same <function-name> can't be used in multiple Replace statements; or by trying to insert a function later in the source by replacing an unrelated and unused late-declared function via the '...replacing "<function-name>" syntax, because in 'Include (- [<function-name>...]; -) replacing "<function-name>".' the two <function-name>s must match, otherwise the function is just included as if the ' replacing "<function-name>" ' clause did not exist.

Veneer functions (see later) do not need a Replace directive- they are automatically replaced by a like-named function declaration in I6 source.
]

Chapter - Including a common property when defining a dummy object, allowing I7 to call a function in a 'splat' or the veneer

[Directly-pasted I6 code (a 'splat') is a 'black box' to I7, meaning that it is 'invisible' to the I7/I6->Inter compiler and also to the Inter->I6 compiler, being set aside and ignored during the I7/I6->Inter->I6 compilation process, then simply pasted verbatim into the final I6 code file 'auto.inf' at the last minute.  This means that any named functions (or other named entities, such as constants, arrays, or newly-declared properties) declared in the splat cannot be referred to from I7, including via ordinary I6 inclusions.  Attempts to do so will fail during I7/I6->Inter compilation with a complaint that the named entity can't be found as a declaration in system functions, kits or I7 source code.

So we need a 'back-door' or 'bridge' from I7 (including normal I6 inclusions) to our 'splat' of I6 code via named entities which:
(i) are already declared and known to the I6->Inter-I6 compilers- either inherently or from kits or I7 source code (so that we can refer to these named entities from I7, or from I6 inclusions in I7 source)
(ii) we can nevertheless subvert to reference named entities in our 'black box' splat without upsetting the I7/I6->Inter->I6 compilers.

One method of prividing this bridge is by bypassing the I7/I6->Inter->I6 compilation process using the 'when defining <object-name>.' syntax for a known I7 object rather than for a kind.

This time, we define for our object one or more of the 'common' properties that are automatically declared by the compiler for all objects and are therefore known to the I6->Inter->I6 compilers. Consequently they can be referenced by I7, or by I6 inclusions in I7. On the other hand, because these property definitions are pasted directly into the final I6 source, they do not themselves trouble the I7/I6->Inter->I6 compilation process and can therefore with impunity reference named entities in our 'black box' splat.

A list of these 'common' properties is given in the Appendix, along with the I7 names and I7 kinds that are declared for some of them in Basic Inform or the Standard Rules. A few additional special compiler-declared common properties such as 'name' are not listed. These, and a few others that are created by the compiler for all objects (article; KD_Count; list_together; plural; short_name; vector) are best not interfered with.



Note that although these properties may have kinds assigned to them in I7, I6 has no concept of kinds and we are free to put any I6 entities we want in these properties- numbers, anonymous functions, I6 text, objects, dictionary words etc.
]
The dummy-object is a dummy-kind.
The dummy-object object translates into I6 as "Dummy_object".


Include (-
	with capacity [ x;					! an anonymous function to print from a character array called Greeting declared in our splat.
		for (x=0: x<Greeting->0:x++){
			print (char) Greeting->(x+1);
		}
	],
	add_to_scope  [ x i;					! similarly, to test our ZSCII character range printing function
		for (x=1: x<=ZSCIIstring->0: x++){
			i=ZSCIIstring->(x);
			print (char) i, " (", i ,"): ", (ZSCII_range) i, "^";
		}
	],
	initial [x; MyFunction(x);], 	   ! an anonymous function taking a parameter and passing it on to a named function declared in our splat
	articles [p; Print__PName(p);],     ! directly calling a veneer function that takes a property id number and prints a textual description of it
	inside_description [o k p x; 		! to print the contents of a superclass property of an unknown nature
		if (~~(o provides p)) rfalse;
		if (~~(o ofclass k)) rfalse;
		x = o.k::p;                                 ! getting the value of a superclass property (::p) of a given class (k) for an object (o) which inherits from it
		if ( metaclass( x ) == Object) print (name) x;
		else if (metaclass(x)== Class) print (I7_Kind_Name) x;       ! this will only print the I7 kind names of I6 Object subclasses
		else if (metaclass(x)== String) print (string) x;					! text properties created in I6
		else if (x-->0 == CONSTANT_PACKED_TEXT_STORAGE) TEXT_TY_Say(x);    ! text properties created in I7
		else if (metaclass(x-->1) == Routine) (x-->1)();                 ! if it's a routine, just run it- it may or may not print anything
		else print x;  ! default
 	],
	grammar [o k p x;					! simply returning the I6 value of the contents of the superclass property.
		if (~~(o provides p)) rfalse;
		if (~~(o ofclass k)) rfalse;
		return o.k::p;
	],
										
	                                                                            
-) when defining the dummy-object.


Section - Accessing a superclass property for an object

[due to a bug introduced in Ver 10, I7 phrases with a property parameter can only be declared with an I6 definition, not with a following I7 code block, so we finesse this by instead using a number parameter which is the I6 property id number, derived from an I6 definition:]

To decide what number is (p - a property) property: (- ({p}-->1) -).
[in I7, a property p is actually the address of a word array of metadata relating to p, in which the second element, -->1, holds the id of the I6 property]

[now '<property> property' e.g. 'plural printed name property' can be used for '(n - a number)' in a To.. phrase]

[in similar vein, there seems to be no obvious way to pass an I7 kind as a parameter to either an I7 or I6 phrase. Literal kinds can be pasted in as, for example (+ person +), but not a kind variable such as (+ K +)).  Here are demonstrated 2 ways to finesse this.
(i) by in the first instance passing a text parameter- being the name of the kind we're interested in- to an I7 phrase. We then use that text to look up in a table (built before play begins from the I6 KindHierarchy array) the I6 memory address representing the corresponding I6 class. Finally, we pass our property-id-as-number, our kind-as-I6-class-address-as-number and our object to the Dummy_object property routine that will process the requested superclass property.
(ii) somewhat less convoluted, by representing the kind parameter from the start not by a text of its name but directly by the I6 address number that represents it, pulled from a sequence of phrases like'To decide which number is...: (- ((+ person +)) -). (see Section - Text and numeric constants for kind names, below). We could do the same using a translated kind name instead of a (+ ... +) insertion, except that translation of kinds is currently broken in Ver 10.1.2 (it has been fixed for the next release). Then, as before, we pass our property-id-as-number, our kind-as-I6-class-address-as-number and our object to the Dummy_object property routine that will process the requested superclass property.
]
[method (i)]
To say the superclass (p - a number) of (t - a text) for (o - an object):
	if t is a name listed in the Table of Kind-Values:
		let k be the value entry;
		say the I6 superclass p of k for o.
To say the I6 superclass (p - a number) of (k - a number) for (o - an object): (- Dummy_object.inside_description({o},{k},{p}); -).
		
To decide which C is the superclass (name of kind of value C) for the (p - a number) of (t - a text) for (o - an object):
	if t is a name listed in the Table of Kind-Values:
		let k be the value entry;
		let n be the I6 superclass p of k for o;
		decide on n as a C.    [we know the kind we're expecting, so just cast the numeric contents of the superclass property back to that kind]
To decide which number is the I6 superclass (p - a number) of (k - a number) for (o - an object): (- Dummy_object.grammar({o},{k},{p}); -).

[method (ii)]
To say the superclass (p - a number) of (k - a number) for (o - an object): (- Dummy_object.inside_description({o},{k},{p}); -).

To decide which C is the superclass (name of kind of value C) for the (p - a number) of (k - a number) for (o - an object):
		let n be the I6 superclass p of k for o;  [this was declared under method (i)] [We need this intermediate function and result to do the cast]
		decide on n as a C.    [we know the kind we're expecting, so just cast the numeric contents of the superclass property back to that kind]

		

Section - Other I7 phrases using subverted properties of the dummy-object

To say a/-- greeting via the/-- dummy-object: (- Dummy_object.capacity(); -).
To execute my function for (n - a number): (- Dummy_object.initial({n}); -).
To say ZSCII ranges via the/-- dummy-object: (- Dummy_object.add_to_scope(); -).
To decide which number is the/-- property id of (p - a property): (- ({p}-->1) -).
[in I7, a property p is actually the address of a word array of metadata relating to p, in which the second element, -->1, holds the id of the I6 property]
To say the/-- I7 name of (p - a property): (- print (string) {p}-->3 -).
[in I7, a property p is actually the address of a word array of metadata relating to p, in which the fourth element, -->3, holds a pointer to an I6 static string holding the textual name of the property]
To say a/-- veneer description of (p - a property): (- Dummy_object.articles(({p}-->1)); -).  [ {p} is the address of a word array of metadata relating to the I7 property,  in which the second element, -->1, holds the actual I6 property id to be passed to the veneer function]

Chapter - Testing Include ___ when defining ___

Section - Test phrases

When play begins:
	say "[bold type]Call to a 'splat' function printing from a string Array declared with double-quoted text and with conditional compilation[roman type][line break]";
	say "[a greeting via the dummy-object][paragraph break]"; [calls a splat routine to print some text]
	say "[bold type]Calls to 'common property' functions printing/retrieving the value of a superclass property[roman type][paragraph break]";
	say "[italic type]method (i):[roman type][paragraph break]";
	[say the superclass property of a kind for an object that inherits from it, when we don't know what the nature of the superclass property is- I7 packed text, I6 String, I6 Routine, I6 Class, I6 Object), integer value]
	say "The plural of person is ['][the superclass printed plural name property of person kind for Smaug]['].[paragraph break]";
	[retrieve the value (of a known kind) in the superclass property of a kind for an object that inherits from it]
	let obj be an object;
	now obj is the superclass object for the lair property of person kind for Smaug;
	say "The default lair of a person is [The obj].[line break]";
	[or just retrieve the value and say it all in one go]
	say "The default lair of an animal is [The superclass object for the lair property of animal kind for Smaug].[paragraph break]";
	say "[italic type]method (ii):[roman type][paragraph break]";
	[say the superclass property of a kind for an object that inherits from it, when we don't know what the nature of the superclass property is- I7 packed text, I6 String, I6 Routine, I6 Class, I6 Object, integer value)]
	say "The usual hit-points of a person is [the superclass hit-points property of person-kind for Smaug].[line break]";
	let num be a number;
	now num is the superclass number for the hit-points property of animal-kind for Smaug;
	say "The usual hit-points of an animal is [num].[line break]";
	[retrieve the value (of a known kind) in the superclass property of a kind for an object that inherits from it]
	say "The usual hit-points of a dragon is [The superclass number for the hit-points property of dragon-kind for Smaug].[paragraph break]";
	[or just retrieve the value and say it all in one go]
	say "[bold type]A loop calling a 'splat' routine that uses the 'to' switch syntax disallowed in I7 inclusions[roman type][line break]";
	repeat with n running from 10 to 13:   [ repeatedly call a splat routine that uses 'switch' syntax disallowed in I7 inclusions]
		say "The mission Apollo [n] ";
		execute my function for n;
		say ".";
	say line break;
	say "[bold type]A system function (random()) replaced through the Replace directive[roman type][line break]";
	say "Today's not very random number is [a random number between 1 and 10000].[paragraph break]"; [use a Replaced system function]
	say "[bold type]Calling a veneer function[roman type][line break]";
	say "The ['][italic type][I7 name of printed name][roman type]['] property has id [italic type][property id of printed name][roman type] and veneer description [italic type][veneer description of printed name][roman type].[paragraph break]"; [call a veneer function]
	say "[bold type]Another 'splat' routine that uses the 'to' switch syntax, tabulating the ZSCII range of some ZSCII characters [roman type][line break]";
	say ZSCII ranges via the dummy-object;
	
Section - Using a stub and other approaches that don't work

[ Using a stub, or the use of #ifndef or other conditional compilations in I6 inclusions, fails because any conditional compilation declared in normal I6 inclusions is performed by the I6->Inter compiler and so the conditional compilation directives are not passed through the I6->Inter->I6 compilation chain to the final I6 source file. This means that either the conditionally-compiled named entity is compiled by the I6->Inter compiler, in which case it will end up duplicating any separate declaration in a 'splat', or it is not, in which case the compilation will fail through the attempt to reference an undeclared named entity. So, if we try an Include such as-

Include (- #Stub ZSCII_range 1; -).

To say a/the/-- ZSCII range of (n - a number) via the/-- stub: (- print (ZSCII_range) {n}; -).

When play begins: say the ZSCII range of 209 via the stub.

-in order to allow reference in an I7 inclusion to a like-named routine ('ZSCII_range') included elsewhere 'when defining...' in a 'splat', the stub routine [ ZSCII_range x1; rfalse; ]; is duly compiled into the final I6, which ends up with duplicate 'ZSCII_range' functions with no conditional compilation.

Unfortunately, the dummy routine compiled from the stub always appears later in the final I6 than the 'splat', its position in the code being determined by the compiler with no authorial control. Consequently a Replace directive in the 'splat' can't rename the dummy stub routine, or replace it with the genuine one. A Replace directive can only rename or remove the earlier-occuring genuine routine declared in the 'splat'.

The end result is that when Inform attempts to compile the resulting 'auto.inf' file into a story file, a compiler error ensues when the stub-derived second declaration of 'ZSCII_range' is encountered: 'Error:  "ZSCII_range" is a name already in use ...'

(Attempts to'trick' the compiler by including 'Constant ZSCII_range 0;' also fail.  The I6->story file compiler may have little to no type recognition, but the I6->Inter compiler does, and recognises that 'ZSCII_range' should be a routine, not a constant. Compilation is aborted with ...'attempt to retrieve wrong pointer type as vanilla_function...')

Ultimately, for a 'ZSCII_range' routine to be directly referenced in an ordinary I6 inclusion, a 'ZSCII_range' routine must be compiled by the I6->Inter->I6 compilation chain, and this will inevitably result in a duplicate ZSCII_range routine in the 'auto.inf' file if one is also declared in a 'splat'.

It is of course possible as a last resort, but a bit of a nuisance, to hand-edit the resulting faulty auto.inf file to remove the duplicate dummy stub routine, then recompile auto.inf directly from the command line.
]

Part - Kinds
[
									“Never lose a chance of saying a kind word.”

											— William Makepeace Thackeray
]
[Some functions to facilitate the handling of kinds, which appear not to be a kind of values in Inform 7 and cannot therefore be iterated through, said, passed as parameters etc.]

Chapter - Object Kinds

Section - The KD-count property

An object has a number called KD-count.
The KD-count property translates into I6 as "KD_Count". 
[This compiler-defined  property is the index to the KindHierarchy array for the kind of this object]

Section - Text and numeric constants for kind names

[These are for use in phrases such as   'To say the superclass (p - a number) of (t - a text) for (o - an object):'   where t is the I7 name of a kind]

[constant texts for built in kinds]
room kind is always "room".
thing kind is always "thing".
direction kind is always "direction".
door kind is always "door".
container kind is always "container".
supporter kind is always "supporter".
backdrop kind is always "backdrop".
person kind is always "person".
region kind is always "region".
man kind is always "man".
woman kind is always "woman".
animal kind is always "animal".
device kind is always "device".
vehicle kind is always "vehicle".
player's holdall kind is always "player's holdall".
[add constant texts for new kinds below (using their Inform 7 kind names)]
dragon kind is always "dragon".
dummy-kind kind is always "dummy-kind".
dummy-count-kind kind is always "dummy-count-kind".

[directly finding I6 memory addresses representing built in kinds]
To decide what number is room-kind: (- ((+ room +)) -).
To decide what number is thing-kind : (- ((+ thing +)) -).
To decide what number is direction-kind: (- ((+ direction +)) -).
To decide what number is door-kind: (- ((+ door +)) -).
To decide what number is container-kind: (- ((+ container +)) -).
To decide what number is supporter-kind: (- ((+ supporter +)) -).
To decide what number is backdrop-kind: (- ((+ backdrop +)) -).
To decide what number is person-kind: (- ((+ person +)) -).
To decide what number is region-kind: (- ((+ region +)) -).
To decide what number is man-kind: (- ((+ man +)) -).
To decide what number is woman-kind: (- ((+ woman +)) -).
To decide what number is animal-kind: (- ((+ animal +)) -).
To decide what number is device-kind: (- ((+ device +)) -).
To decide what number is vehicle-kind: (- ((+ vehicle +)) -).
To decide what number is player's-holdall-kind: (- ((+ player's holdall +)) -).
[add To decide phrases for new kinds below (using their Inform 7 kind names)]
To decide what number is dragon-kind: (- ((+ dragon +)) -).
To decide what number is dummy-kind-kind: (- ((+ dummy-kind +)) -).
To decide what number is dummy-count-kind-kind: (- ((+ dummy-count-kind +)) -).


Section - The number of kinds and the table of kind values

[This section
(i) initialises a variable to hold the total number of kinds compiled into the story file, so we can iterate through kinds
(ii) initialises a table holding low-level information about all the kinds, in a form readily-accessible to Inform 7]

The number of kinds is a number that varies.
The number of kinds variable translates into I6 as "kind_count".
Include (- Global kind_count = 0; -).

A startup rule (this is the initialise kinds rule):
	count the kinds;
	fill the table of kind-values.
	
The initialise kinds rule is listed before the update chronological records rule in the startup rules.

To count the kinds: 
	now the number of kinds is 0;
	repeat with o running through objects:
		if o provides the property KD-Count:
			let kdc be the KD-Count of o;
			if kdc > the number of kinds:
				now the number of kinds is kdc;
[Inform does not compile a constant for the count of object kinds, so we must count them ourselves]
[We can't get this information from the KindsHierarchy array either, because it has no length entry or terminating entry.]
[NB This method only works properly if there is at least one object compiled with the highest kind id number (KD-Count)- this can be achieved by declaring a dummy kind and an object of that kind right at the end of the I7 source (see Section - Dummy-count-kind and -object later)]

	
To fill the table of kind-values:
	let nk be the number of kinds;
	while nk > 0:
		choose a blank row in the Table of Kind-Values;
		now the name entry is the substituted form of "[kind-name of kind-index nk]";
		now the index entry is nk;
		now the value entry is the value of kind nk;
		now the inherits entry is the kind-index nk inherits from;
		decrement nk;
	[showme the contents of the Table of Kind-Values;] [comment this line out when not debugging]
		
Table of Kind-Values
name (text)	index(number)	value(number)	inherits(number)
with 25 blank rows [extend this if necessary]
[name is the I7 name of the kind;
index is the kind id number, as held in KD_Count for each object and being an index into the I6 KindHierarchy array;
value is the I6 memory address of the metadata representing this kind: this can be passed to I6 phrases needing a class parameter
inherits is the kind id number of the kind that this kind most immediately inherits from]

Section - Functions from the Table of Kind-Values	

To decide what text is the kind-name of the kind-address (n - a number):
	if n is a value listed in the Table of Kind-Values:
		decide on the name entry;
	decide on "";
	

To decide what number is the kind-address of (o - an object):
	if o is nothing:
		decide on 0;
	if the KD-count of o is an index listed in the Table of Kind-Values:
		decide on the value entry;
	decide on 0;


	
Section - Functions from the kind hierarchy
	
To say the/-- kind-name of kind-index (n - a number): (- print (I7_Kind_Name) KindHierarchy-->({n}*2); -).
To decide which number is the/-- kind-index (n - a number) inherits from: (- (KindHierarchy-->(({n}*2)+1)) -).
To decide which number is the value of kind (n - a number): (- (KindHierarchy-->({n}*2)) -).
		
To tabulate the kind hierarchy:
	let nk be the number of kinds;
	while nk > 0:
		let ik be the kind-index nk inherits from;
		say "[nk]: [kind-name of kind-index nk] ([value of kind nk]) -> ([kind-name of kind-index ik][if ik > 0]-[ik][end if])[line break]";
		decrement nk;
		
To tabulate the kind hierarchy of (o - an object): (- PrintHierarchy({o}); -).
Include (-
[PrintHierarchy o i a;
	print "(", (name) o, ")";
	if (o provides KD_Count) {
		i = o.KD_Count;
		while (i > 0) {
			a = i*2;
			print "->", (I7_Kind_Name) KindHierarchy-->a;
			i = KindHierarchy-->(a + 1);
		}
	}
];


-).

Chapter - Testing Kinds

When play begins:
	say "[line break][bold type]----------------------- ### Testing Kinds Initialisation ### ---------------------[roman type][paragraph break]";
	say "[bold type]Tabulating the kind hierarchy of an object[roman type][line break]";
	tabulate the kind hierarchy of Smaug;
	say paragraph break;
	say "[bold type]The object kinds hierarchy (I6 KindHierarchy array)[line break]Index: name (I6 memory address) -> (inherits from name-index)[roman type][line break]";
	tabulate the kind hierarchy;
	say "(The number of object kinds is [number of kinds])[paragraph break]";
	showme the contents of the Table of Kind-Values;
		
Part - Miscellaneous


Section - Casting

To decide which K is the (to-be-cast-value - a value) as a (name of kind of value K):   (- {to-be-cast-value} -).

Section - Dummy-count-kind and -object (put this last in source)

dummy-count-kind is a kind.
dummy-count-object is a dummy-count-kind. [make sure there is at least one object with a kind-index above the base kinds]


Part - Appendix

Section - Common properties declared by Basic Inform and Standard Rules and their I7 equivalents

[
	I6 name				I7 name & kind			comments		
	_________________			__________					___________________________________________________________________
	
	add_to_scope;			----
	article;				indefinite article (text)		(do not redefine)
	articles;				----
	capacity;				carrying capacity (number)
	component_child;		----
	component_parent;		----
	component_sibling;		----
	description;			description (text)
	door_dir;				----
	door_to;				----
	found_in;				----
	grammar;				----
	initial;				initial appearance (text)
	inside_description;		----
	list_together;			list grouping key (text)		(do not redefine)
	map_region;			map region (object)
	parse_name;			----
	plural;				printed plural name (text)	(do not redefine)
	regional_found_in;		----
	room_index;			----
	saved_short_name;		----
	short_name;			printed name (text)			(do not redefine)
	short_name_indef;		----
	vector;				----						(do not redefine)
	with_key;				matching key (object)
	KD_Count;			----						(do not redefine)
	IK1_Count;			----
	IK2_Count;			----
	IK3_Count;			----
	IK4_Count;			----
	IK5_Count;			----
	IK6_Count;			----
	IK8_Count;			----
	IK1_link;				----
	IK2_link;				----
	IK5_link;				----
	IK6_link;				----
	IK8_link;				----
]

Section - I6 Directives not recognised in kits or inclusions in V10+

[	
### NOT RECOGNISED ###
	Ifv3
	Ifv5
	Iffalse
	Abbreviate
	Dictionary
	Import
	Link
	Lowstring
	Message
	Replace
	Switches
	Trace
	Undef
	Version
### RECOGNISED ###
	#Ifdef		(this and similar conditional compilation block markers will not be transmitted to the final I6 source file)
	#Ifndef
	#Iftrue
	#Ifnot
	#Endif
	#OrigSource
	#Stub		(this and similar conditional compilation block markers will not be transmitted to the final I6 source file)
	Constant
	Global
	Array		(a number of previously-legal forms are no longer supported, in particular string type & double-quoted declarations like "Hello")
	Attribute
	Property
	Verb                 (using this in an inclusion leads to "found a second definition of the name 'assim_gv' when loading '/main/WorldModelKit' "
	Fake_action
	Object
	Default		(this and similar conditional compilation block markers will not be transmitted to the final I6 source file)
]

2 Likes

This week’s prize, Bubbling Beaker Award® #8 is presented to @SJ_43, who didn’t spend much time on the forum but left a gem of an example behind. SJ_43’s award-winning post demonstrates a compact method for handling the player character telling an NPC to tell a third NPC to do something, as in >PATIENT, TELL DOCTOR TO EXAMINE YOU.

You might look it over and ask “Where’s the ‘mad’ part? That code doesn’t even use I6!”, but the genius here is running a line of code through the existing machinery in a non-obvious and minimally disruptive way.

As a bonus, on a related post in the same thread, SJ_43 shows a technique for having an NPC issue an order to another NPC in a manner governed by persuasion-like rules.

8 Likes

Bubbling Beaker #8 is from 2009.

I’d like to nominate @otistdog for the Intrepid Archaeologist Award to honour the effort of digging up dangerously unstable bubbling beakers from under the geological layers and unearthing ancient mad science concoctions.

5 Likes

The code in the link uses some outdated syntax and no longer compiles.

This works:

Summary
"Instructing_Updated" by PB

The Lab is a room. 
The doctor is a woman in the lab. The patient is a man in the lab.

The instructor is a person that varies.
The instructee is a person that varies.
The instructed command is a text that varies.
The instruction stage is a number that varies.
["instructing" is only true when an NPC (the instructee) is acting by order of another NPC (the instructor)]
To decide whether instructing: if the instruction stage is positive, yes; no.

Before reading a command when instructing: decrease the instruction stage by one.
For reading a command when instructing:
	change the text of the player's command to "[instructee], [instructed command]";
	[so that "tell doctor to jump" works just like "doctor, jump": ]
	if the instructor is the player, now the instruction stage is 0;

Ordering it to is an action applying to one thing and one topic.
Understand "tell [someone] to [text]", "ask [someone] to [text]" or "order [someone] to [text]" as ordering it to.
Understand the command "instruct" as "order".

Carry out an actor ordering someone to:
	now the instruction stage is two;
	now the instructor is the person asked;
	now the instructee is the noun;
	now the instructed command is the topic understood;

[To keep the every turn rules, advance time rule, etc. from running twice: ]
The skip to next turn before instructing rule is listed before the every turn stage rule in the turn sequence rulebook.
This is the skip to next turn before instructing rule: if the instruction stage is greater than one, rule succeeds.

[To avoid "There is no reply" if we type "doctor, tell patient to adsfasd": ]
Instead of answering someone that when instructing, say "[The instructor] gives you a confused look."

Understand "herself/himself" as a person when instructing and the item described is the instructee.
Understand "you" as a person when instructing and the item described is the instructor.

A first persuasion rule for asking someone to try ordering someone to: persuasion succeeds.

Persuasion rule when instructing (this is the NPC persuades NPC rule):
	if the instructor is a woman and the instructee is a man, persuasion succeeds;
	if the instructor is the instructee, say "That's too complicated." instead;
	say "[The instructee] ignores [the instructor]." instead;

[Persuasion rules that are only for when the player asks an NPC need "when not instructing" appended to them: ]
Persuasion rule for asking someone to try doing something when not instructing: say "[The actor] sniffs. 'The only thing I'll do for you is tell other people what to do.'" instead.



Test me with "tell doctor to examine patient / patient, tell doctor to examine you / doctor, tell patient to exmaine himself/ doctor, tell patient to examine himself".
4 Likes

Thanks! I found it a while ago and had forgotten that it took some tinkering to update.

2 Likes

This week’s prize, Bubbling Beaker Award® #9 is presented to @Dannii, who is not a mad scientist but does very admirable work plumbing the depths of Inform’s internal machinery. This award is not for the post itself but for the extension that it announces, called Data Structures by Dannii Willis. This extension creates some entirely new data types for use in Inform 6M62 – no mean feat in a time before Inform 7 was made open source. As I understand it (which is not very well), the net effect is to loosen the typing restrictions of Inform somewhat, allowing values that work like weakly-typed variables, plus other kinds of value that offer affordances found in other programming languages (e.g. dictionaries, nullable values, two-value structs, and others). (NOTE: My understanding is that, unfortunately, the extension does not work in Inform 10 due to certain incompatibilities. It does work for 6M62.)

Congratulations, Dannii! The floor is yours if you would like to talk a little bit about your creation. Perhaps you can share a little about what motivated its creation, what makes it possible, the kinds of hurdles that you had to overcome in order to write it, and the prospects of a version compatible with Inform v10.

11 Likes

Thank you for this honour! Data Structures will surely be my Inform 7 magnum opus, I doubt I’ll ever make anything else for I7 that’s so ambitious.

So Data Structures was the third iteration, the first being the JSON extension, and that explains the initial motivation: I wanted to make an extension to handle parsing and generating JSON. And I wanted to do that because I thought it would be a better data format for a lot of situations than what Inform currently has, which is to use tables, even if the data doesn’t really fit into a two dimension array. The big problem with the JSON extension is that users would have to manually clean up the memory, and no one wants to have to manually call mfree all the time!

The second iteration was the Collections extension. The big change here was that the kinds you could put into them were not limited to the JSON types, but any (sayable) I7 kind. Map keys no longer had to be texts as they did in the JSON extension. But you still had to manually clean up.

The restriction of sayable kinds is because of how I implemented the any kind (called a collection reference): in order to store heterogenous data in these data structures, we need a way for values to carry their own kind IDs along with them. So Collections uses malloc to create a two value block, putting the kind ID in the first word, and the value itself in the second. But how do you get a kind ID in I7? The first kind ID that I found was to use the {-printing-routine:K} invocation, essentially the function for printing each kind. This worked, but did mean the system had to be limited to sayable kinds. As I was working on Data Structures I realised I could instead use the {-strong-kind:K} invocation, meaning that non-sayable kinds could also be used. And for composite kinds (like a list of K, or one of these new data structures), the strong kind actually refers to a I6 array that stores all the sub kinds. So if you make a “couple of (list of anys) and (map of objects to (map of numbers to list of texts))” then all of those kinds are stored and can be evaluated, no matter how deeply nested. The printing routine invocation would only give you the ID for “list” or “map” or “couple”, which is obviously much less useful.

So finally we come to the Data Structures extension. If there’s anything really impressive that I did, it’s that I slowly worked out how to define new block values without there being any documentation for how to do so. This took a lot of trial and error messing around with kind definitions in the Load-*.i6t templates (now called the Neptune language.) By using block values we removed the need to clean up manually, instead I7’s reference counting would take care of it all for us. It also meant that the new kinds would be much better integrated into the existing kind system; JSON and Collections both needed to have an array kind, but Data Structures doesn’t, instead the standard list kind is used, and integrates smoothly with the new kinds. Need an array of heterogenous kinds? Just use a list of anys!

While I was working on Data Structures I’d also been using Rust and TypeScript more, and had come to see how useful algebraic data types were, specifically sum types (also known as tagged unions). While I7 doesn’t yet support generic sum types, I did add some specific examples of sum types, namely the option and result kinds. Options let you store either a value or a null value, and are a type safe way of storing optional values, allowing you to distinguish 0 from NULL. Results let you store a success value or an error text message, also called checked exceptions. By returning a result you can require that any users of a phrase must check if it succeeded, so that errors can’t be ignored.

I also added a few other data structures I thought could be useful. Couples let you return two values from a phrase. (I wanted to make variable length tuples, but it wasn’t possible in 6M62. It might be possible in the future.) Promises represent a value yet to be determined, and will be familiar to many other languages. But the most ambitions and hacky was the closure kind, allowing you to freeze the state of a function and resume it later on. This required implementing a partial Quetzal parser in order to identify how many locals there are, amongst other nasty hacks. It’s truly gnarly.

There were huge changes to Inform across the board between 6M62 and version 10, so progress for an Inform 10 version of Data Structures is slowly ongoing. I’ve been prioritising getting the Glk system ready, but periodically check if Inform 10 can now handle everything needed for DS. We’re not quite there yet, but maybe soon. And it will possibly also be included as one of the built in kits. I’m also keen to change its implementation of maps from the naive linear search it currently has to a much more efficient binary search, though how to handle block values is an open question.

I’m not sure what else to say. Anyone have any questions about it?

14 Likes