Bubbling Beaker Awards (Award #24, May 03 2024)

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

PS, I want to claim mad scientist title for this abomination :stuck_out_tongue:

6 Likes

That’s horrible. I’m in awe.

4 Likes

This week’s prize, Bubbling Beaker Award® #10 is presented to @matt_weiner, who was at one point a very frequent contributor to the forum but has not been by for several years. (Honorable mention is given to @Skinny_Mike, who provided significant technical assistance at how and when does I7 guess which indirect article to use? - #19 by Skinny_Mike) Matt’s award-winning post concerns a method of improving Inform’s handling of irregular indefinite article selection in the context of conditional printed name output. The running example is a unicorn, which may or may not be honest – out-of-the box, I7 will prefer “an unicorn” and “a honest unicorn” when printing the name of this animal.

The code wasn’t quite perfected in the original post, but I’ve taken the liberty of updating and streamlining it a bit. Details will be given in a follow-up post.

3 Likes

Why is this interesting? Because I7’s default method of choosing whether to use “a” or “an” as an indefinite article is quite prone to error when faced with words that have irregular pronunciation. In short, it always picks “a” when the word starts with a consonant and “an” when the word starts with a vowel. (Note that the only vowels recognized by I7 are a, e, i, o and u, so y always counts as a consonant.)

Although the template code is good about ensuring that the entire printed name is considered (including anything prefixed via a Before printing the name... rule), as well as the number word for a plural group of identical objects such as “six hourglasses,” it has no provision for deeper phonetic analysis. English is just about the worst language for attempting to predict pronunciation from spelling, so the reason for this gap is easy to appreciate.

The solution presented is not particularly elegant; all it does is create exception tables that are checked when deciding which indefinite article to use. Any word that the author wants to act differently than the default is simply added to a table. Performance is not great, but it’s not a major problem for the limited number of exceptions that any one story is likely to require.

The following example code is for 6M62, but conversion to 10.1 is straightforward. Note that it is written for Glulx only:

An Honest Unicorn
"An Honest Unicorn"

The Lab is a room.

An animal can be honest or dishonest. Understand the honest property as describing an animal.

Before printing the name of an animal (called beast):
	say "[if beast is dishonest]dis[end if]honest ".

The unicorn is an honest animal.
The yak is a dishonest animal.

A block is a kind of thing. It has a number called weight. The description of a block is “It looks to be about [weight of the item described in words] pound[s].”

After examining a block (called B):
	now the indefinite article of B is "";
	now the printed name of B is “[weight of B in words]-pound block of [B]”.

The yttrium is a block with weight one. The indefinite article of the yttrium is "some".

The uniform is wearable. The description is “It’s a United States Army uniform.”

After examining the uniform for the first time:
	now the printed name of the uniform is “United States Army uniform”.

A unicorn, a uniform, an hourglass, a yak and yttrium are in the lab. An Æsop anthology is in the lab.
A rock, a pebble, and an umbrella are in the lab.

Table of Words That Start With Vowel Sounds
word
“Æsop”
“hour”
“hourglass”
“honest”
“yttrium”

Table of Words That Don’t Start With Vowel Sounds
word
“one”
“uniform”
“unicorn”
“united”
“United”

To decide whether (string - a text) starts with a vowel sound (this is vowel sound checking):
	let the first word be punctuated word number 1 in string;
	if the first word is a word listed in the Table of Words That Start With Vowel Sounds, yes;
	if the first word is a word listed in the Table of Words That Don’t Start With Vowel Sounds, no;
	if character number 1 in the first word is a vowel, yes;
	no.

To decide whether (letter - a text) is a vowel:
	let lower be letter in lower case;
	if lower is:
		-- "a": yes;
		-- "e": yes;
		-- "i": yes;
		-- "o": yes;
		-- "u": yes;
		-- otherwise: no.

The initial sound rules are a rulebook. The initial sound rules have outcomes vowel and consonant.

An initial sound rule (this is the basic initial sound test rule):
	let temp be the substituted form of “[I6 buffer]”;
	if temp starts with a vowel sound:
		vowel;
	otherwise:
		consonant.

To say the/-- I6 buffer:
	(- PrintI6Buffer(); -).

Include (-

[ PrintI6Buffer len i;
	len = StorageForShortName-->0;
	for ( i = WORDSIZE : i < len + WORDSIZE : i++ ) {
		glk_put_char(StorageForShortName->i);
	}
];

-).

[The only change of interest in this inclusion is to routine PrefaceByArticle]
Include (-

Global short_name_case;

[ PrefaceByArticle obj acode pluralise capitalise i artform findout artval;
	if (obj provides articles) {
		artval=(obj.&articles)–>(acode+short_name_case*LanguageCases);
		if (capitalise)
			print (Cap) artval, " ";
		else
			print (string) artval, " ";
		if (pluralise) return;
		print (PSN__) obj; return;
	}

	i = GetGNAOfObject(obj);
	if (pluralise) {
		if (i < 3 || (i >= 6 && i < 9)) i = i + 3;
	}
	i = LanguageGNAsToArticles-->i;

	artform = LanguageArticles + 3*WORDSIZE*LanguageContractionForms*(short_name_case + i*LanguageCases);

	#Iftrue (LanguageContractionForms == 2);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms == 3);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms == 4);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
	if (artform-->(acode+6) ~= artform-->(acode+9)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms > 4);
	findout = true;
	#Endif; ! LanguageContractionForms

	#Ifdef TARGET_ZCODE;
	if (standard_interpreter ~= 0 && findout) {
		StorageForShortName-->0 = 160;
		@output_stream 3 StorageForShortName;
	if (pluralise) print (number) pluralise; else print (PSN__) obj;
	@output_stream -3;
	}
	#Ifnot; ! TARGET_GLULX
	if (findout) {
		if (pluralise)
			VM_PrintToBuffer(StorageForShortName, 160, EnglishNumber, pluralise);	! MODIFIED
		else
			VM_PrintToBuffer(StorageForShortName, 160, PSN__, obj);	! MODIFIED
	}
	#Endif; ! TARGET_

	acode = acode + 3*LanguageContraction(StorageForShortName + WORDSIZE);	! MOVED
	Cap (artform-->acode, ~~capitalise); ! print article
	if (pluralise) return;
	print (PSN__) obj;
];

-) instead of “Object Names II” in “Printing.i6t”.

[The only change of interest in this inclusion is to routine LanguageContraction]
Include (-

Constant LanguageAnimateGender = male;
Constant LanguageInanimateGender = neuter;
Constant LanguageContractionForms = 2; ! English has two:
! 0 = starting with a consonant
! 1 = starting with a vowel
[ LanguageContraction text    result rv i;
	!!! This is the old routine:
	!if (text->0 == ’a’ or ’e’ or ’i’ or ’o’ or ’u’
	!or ’A’ or ’E’ or ’I’ or ’O’ or ’U’) return 1;
	!return 0;
	!!! Out w/ old – in w/ new:
	rv = FollowRulebook( (+ initial sound rules +), nothing, true );	! suppress line breaks
	if ((rv) && RulebookSucceeded()) {
		result = ResultOfRule();
		if (result == (+ vowel outcome +)) return 1;
		return 0;
	}
	return 0;
];

Array LanguageArticles -->
! Contraction form 0: Contraction form 1:
! Cdef Def Indef Cdef Def Indef
"The " "the " "a " "The " "the " "an " ! Articles 0
"The " "the " "some " "The " "the " "some "; ! Articles 1

! a i
! s p s p
! mf n m f n m f n m f n
Array LanguageGNAsToArticles --> 0 0 0 1 1 1 0 0 0 1 1 1;

-) instead of “Articles” in “Language.i6t”.

The code is oriented around special cases, so to make use of it you’ll want to update the two tables that handle these. The Table of Words That Start With Vowel Sounds is for words like “honest” that begin with a written consonant but spoken vowel. The Table of Words That Don’t Start With Vowel Sounds is for words like “unicorn” that begin with a written vowel but a spoken consonant.

As seen in the example code, special characters depicting ligated vowels can also be handled.

3 Likes

What happens when an American and an Englishman converse about “herbs” in general in the game?

4 Likes

This week’s prize, Bubbling Beaker Award® #11 is presented to @Draconis, which marks his second win. His award-winning post lays out a method for allowing the player to issue commands to multiple NPCs in a group, a la Infocom’s Suspended. I haven’t delved into the technical details, but from what I understand about the parser, this is not a simple modification. This work was developed into a public extension.

Congratulations, Draconis! Perhaps you would be willing to say a few words about what it took to get this to work?

[An important note: Award frequency will be decreasing to every other week between now and the end of the year, so the next award will be for Fri Nov 17.]

5 Likes

Oh, man, this one was a while ago! I need to refresh my memory for how this works!

Okay. Let’s imagine we’re giving the command ALICE AND BOB, GO NORTH.

The way the parser works by default, if it sees a comma, it jumps back to the beginning of the command and calls NounDomain. NounDomain is the routine that tries to parse the name of a single object, so most of the other parsing routines delegate to it.

That’s the bit that I modified: instead of calling NounDomain, I make it instead call ParseToken(ELEMENTARY_TT, MULTI_TOKEN)—that is, try to parse a “[things]” token.

This “[things]” token will fail, because we already know the list of actors ends with a comma—that’s how we ended up here in the first place. (If “[things]” sees something after a comma that it can’t understand, it thinks there’s a bad entry in the list.) But as it works, it puts the list of things it parses into the multiple object list, and it doesn’t clear that out when it fails! So now the multiple object list is {Alice, Bob}.

Once it fails, we go back into Inform 7 by calling the “multiple actor rulebook”, which takes the multiple object list, makes sure it’s a valid list of people, and stores it in a global variable of its own. It then takes the rest of the command—everything that comes after—and sticks this in another global variable. If this rulebook succeeds (the list was valid), then the parser continues after the comma, attempting to parse the rest of the line as a normal command. If it fails, then the parser reacts the same way it originally would if NounDomain failed—deciding the part before the comma isn’t an actor name after all, and trying to parse it as a verb instead (the .NotConversation label).

So now we have the list of intended actors {Alice, Bob} stored in a global variable, and the intended command GO NORTH in another global variable. Once we have this, a “rule for reading a command” takes over. As long as the list of intended actors isn’t empty, we set the player to the first element of that list, and parse the intended command. The player is now Alice, and the parser is given the text GO NORTH.

But wait, we don’t want this action to be performed as the player! We want it to be performed by an NPC! So a “first before doing anything” rule sees what’s happening, resets who the player is, and turns the action into a request: “going north” becomes “asking Alice to try going north”. It executes that action, then removes the first entry from the intended actor list.

This continues until the multiple actor list is empty. Since the command is actually parsed separately for each actor, it can deal with the actors having different surroundings: ALICE AND BOB, TAKE ROCK when each one has a different rock in their room, for example. The “rule for reading a command” also pauses when a disambiguation question is asked, letting the player answer before going back to multicommand processing.

As a side bonus, the extension also creates the action COMMAND [things] TO [text], which I used to test the multicommand machinery. In other words, this extension gives you a way to create new actions that convert to giving orders, which normally is nigh impossible! It just puts the list of actors and the intended command into the appropriate global variables, without any of the parser trickery.

9 Likes

Side note: the way Suspended does it is actually hilariously simple by comparison! It goes like this:

  • When parsing an actor, look for the exact phrase BOTH [person] AND [person]. If so, set the actor to a special “both” value.
  • If the “both” actor is commanded to perform any action except moving Fred, give an error.
  • Make sure all the actors involved are in the location of Fred.

Notably, it never even bothers to check that there are two different people involved! You never need multiple robots to accomplish something, even in the one specific place in the game where this syntax works—you can always say BOTH SENSA AND SENSA, MOVE FRED!

6 Likes

This week’s prize, Bubbling Beaker Award® #12 is presented to @capmikee, who used to be a frequent contributor to the forum. His award-winning post is really just a side note regarding a conversation extension that he was working on, but it demonstrates a method for handling a situation in which the author would like the grammar line for a two-object action to handle only the second noun.

Congratulations to capmikee! Because he has been inactive for so long, I will provide a short write-up in a follow-up post.

[An important note: Award frequency will be continued at every other week between now and the end of the year, so the next award will be for Fri Dec 01.]

6 Likes