[I6] How does Inform 6.33's Replace directive variant really work?

(In advance: I know that it’s different in 6.34, but this question is about 6.33.)

The release notes for Inform 6.33 say:

A previously declared routine can be renamed with a new form of the Replace directive.
For example, if a source file contained

Replace Banner OriginalBanner;

It could then (after including the library) contain this definion of a function:

[ Banner;
    OriginalBanner();
    print "Extra version info!^";
];

The library’s banner code would then be in OriginalBanner().

Does it really work by replacing already-declared routines? Or does it only cause routines with the given old name (and that are in a “system file”) to be compiled to the new name instead?

This is not backed up by anything other than the docs, but since the standard behaviour of the single-argument Replace is simply to entirely ignore the definition of a routine with a matching name in a System_file; included file, I would imagine that the two-argument version is similar except that it pretends the other name had been specified instead and then compiles it normally.

So the OriginalBanner() is a normal call to the existing routine, just with a different name than if you hadn’t replaced it.

(There’s probably a bit of extra magic under the hood so that the compiler knows that both names refer to routines even though one hasn’t been implemented yet.)

Note that the docs require you to declare your intention to Replace before the original routine is actually defined for the first time.

(Actually, the documented behaviour of the two-arg form in 6.34 is technically incorrect – but it’s incredibly unlikely to actually be an issue in practice, especially since the LibraryExtensions object in library 6/11 gives you an alternative.)

What’s the issue?

It says that it sets the first name to point at the last implementation and the second name to point at the first implementation, ignoring all other implementations.

What would probably be more useful is if it instead made a chain, such that:

Replace Banner OldBanner;

[ Banner;
    OldBanner();  ! this calls the one in the library
    print "Extra line 1.^";
];

[ PrintBanner;
    Banner();     ! this calls the one defined below
];

[ Banner;
    OldBanner();  ! this calls the one defined above
    print "Extra line 2.^";
];

i.e. each new implementation of Banner has the name Banner refer to itself and OldBanner to refer to the most recent previous implementation, but only within the body of the routine itself. Anywhere else, Banner refers to the last implementation anywhere (as now) while OldBanner either doesn’t exist or perhaps is left pointing at the second-last implementation (the latter might be useful if you want to support calling it outside of Banner itself).

This would allow easy cascading of extensions while still supporting recursive calls. (Assuming you want it to recurse the same level and below, not start at the top – if you want that, then Banner should refer to the last one even inside the definition of a previous one. I’m not sure which of these is better.)

As I said, though, it mostly doesn’t matter as this sort of thing is mostly useful for multiple includable extensions and I6 doesn’t do that sort of thing as much as I7 does. You wouldn’t really write this sort of code outside of that use case.

Does the compiler not do that?

I agree that an arbitrary chain would be more elegant, but it would be a lot more difficult. It would probably also want a more elegant syntax than “Replace X Y”, which is a cheap trick to support a limited feature. So it’s not on the roadmap.

@mirality, that’s consistent with what I’m seeing in tests on 6.33. So it’s not “previously declared” routines that are affected but “routines declared in subsequent system files”, similar to the pattern of 6.31.

I appreciate the cross-check. And the good news is that Replace functionality was expanded again for 6.34.