I7: Possible to allow author-defined code chunks for relation tasks?

While looking into a recent question I noticed that the I7 compiler seems to be at least a little bit smart regarding its awareness of the code chunks associated with executing various tasks for a relation.

The example in question involves the condition test for player is within nothing, which produces code like:

! True or false?
! [ DoesNotExist x IN[ IN] : is(x, ContainerOf('player')) ]
[ Prop_3 
	...
	objectloop (x ofclass Object){
	    qcn_0++;
	    if ((x == ContainerOf(player))){	! <-- key line
	        qcy_0++;
	    }
	}
	if (qcy_0 == 0){
	    rtrue;
	}
	rfalse;
];

The key line makes use of a routine ContainerOf(), which also appears in the relation handler for the containment relation:

[ Rel_Handler_15 
	...
	switch (task) {
	    RELS_TEST: if (X == ContainerOf(Y)) rtrue; rfalse;	! <-- task for testing whether a relation holds

	...
	}
	rfalse;
];

For the same condition test using an author-written relation, the role of ContainerOf() is displaced by a different generated routine:

for (x=IK2_First: x: x=x.IK2_Link){
    qcn_0++;
    if (((RGuard_f1_7(x)) == player)){		! <-- replaced logic
        qcy_0++;
    }
}

which is also used in the handler for that relation:

[ Rel_Handler_81 
	...
	switch (task) {
	    RELS_TEST: if (Y == (RGuard_f1_7(X))) rtrue; rfalse;	! <-- task for testing whether a relation holds
	}
	rfalse;
];

It seems as though the code generation is modular here, and that some special case code forces use of ContainerOf() as the the “truth-tester” routine instead of a generated one.

It occurs to me that if such special case code exists, then exposing hooks for it could be very useful. It would allow authors to make use of some of the same tricks as core relations, such as ensuring mutual exclusivity between sets of two or more relations (a la spatial relations) or enforcing simultaneous applicability of another relation (e.g. the wearing relation forcing spatial movement when asserted true).

I know Inform doesn’t allow that now, but I’m wondering just how feasible this might be to implement. Depending on the underlying structure, it could be low-effort. Does anyone happen to know where to find the part of the I7 source documentation that covers generation of code chunks for relation tasks?

2 Likes

Inform connects up scope, enclosure, containment, support, wearing, and incorporation (part-of) with a series of terrible hacks, as you describe. Not being able to systematically describe or extend this is an ongoing problem.

Unfortunately, I don’t know anything about how the current hacks work or what I would replace them with. :)


Background: The Z-machine (and later Glulx) provides an object tree with no higher-level structure. Iterating this is efficient, but you need to provide your own concepts of containers, supporters, etc.

I6 added these concepts with flags on the parent objects. This stayed efficient but extending it was a nuisance. Scope additions were clunky. Backdrops were clunky. Also it couldn’t represent the both-container-and-supporter idea. (You could flag an object as both a container and supporter, but there was no way to indicate which sub-objects were contents and which were supportees.)

I7 tried to add relations as a core concept while keeping I6’s parent-object flags (and efficiency). This required jamming some screwdrivers into the relation machinery; they’re still there. And extending it is now actually harder than before, because the screwdrivers are jammed in at the compiler level, not the library level.

3 Likes

The spatial relations are indeed very special all throughout the compiler. However, from what I can glean of the current I7 compiler code, they’re less special in some ways than in others.

The aspect highlighted in this question, the use of ContainerOf rather than a compiler-generated test function, appears to be of the somewhat less-special sort. Some internal code constructing compiler data structures describing the spatial relations (this much is hard-coded and special-cased) simply sets ContainerOf(*1) as “schema” for “getting the unique thing that *1 is in relation with”. Some relations created by authors ultimately also get a schema for doing that. There are a lot of other steps before, between, above, below, etc. that I’m skipping because I don’t fully understand the whole process. Still, it seems pretty clear that, for the two cases discussed here, the code is indeed generated via similar Inter mechanisms.

However, digging in to learn this has also impressed upon me that there’s a dizzying number of moving parts involved in compiling relations and everything related to them. There’s many subtly different pieces of information that factor into it; what goes into the relation handler is only a small part of how any given piece of code gets compiled. In addition, testing relations is intrinsically tied to how they’re represented at runtime (if at all) and how they can be modified at runtime (if at all). I’m not sure where I’d even start with trying to identify a small set of hooks that could allow authors to influence this process without being exposed to all its gory details – many of which are reasonably considered implementation details, subject to change without notice.

I made feature request I7-2474 a while ago.

I’m not sure that I follow what you mean.

Bearing in mind that there are two distinct types of relations (“assertable” and “non-assertable”), the only oddnesses in the system that I’ve seen boil down to one of:

  1. allowing more than one pairing of left/right member kinds for a non-assertable relation

    • example: ContainerOf() allows X contains Y to be true when X is a room or container
  2. forcing another assertable relation B to also hold when an assertable relation A is made true

    • example: WearObject() ensures that now X wears Y means that it is also true X has Y (though this is arguably a side effect of the world model and the truth-testing routine for the relation)
  3. forcing another assertable relation B to not also hold when an assertable relation A is made false

    • example: MoveObject() unasserts any and all incorporation relation assertions regarding the moved item

It certainly seems within the realm of possibility that the compiler could handle many cases of types 2 and 3 automatically if given some contextual information from the author, e.g.

The fooing relation always implies the barring relation.

The plover relation is mutually exclusive with the xyzzy relation and the plugh relation.

For cases of type 1 (which I am here assuming to involve only non-assertable relations) one can imagine:

The containment relation relates a room (called R) to a thing (called T) when the parent of T is R.
The containment relation relates a container (called C) to a thing (called T) when the parent of T is C.

and the compiler then knows what type of tests to allow and what logic to use depending on the kinds involved in a condition.

Any other low-level details might take a rule-like form:

To set the I6 worn attribute for (T - thing):
	(- give {T} worn; -).

When asserting the wearing relation between a thing (called T) and a person (called P):
	now T is part of nothing;
	set the I6 worn attribute for T.

This makes a lambda-like code chunk to be added to the routine for the RELS_ASSERT_TRUE task. (Zed’s suggestion of an activity-like treatment would give even more flexibility here, but I wasn’t imagining any kind of runtime conditionality.)

Ideally it would be possible to leverage this type of system to allow assertion of otherwise unassertable relations, where necessary or desirable:

To I6 move (O1 - object) to (O2 - object):
	(- move {O1} to {O2}; -).

When asserting the containment relation between a room (called R) and a thing (called T):
	now T is part of nothing;
	I6 move T to R.

When asserting the containment relation between a container (called C) and a thing (called T):
	now T is part of nothing;
	I6 move T to C.
1 Like