Inform 7 v10.1.0 is now open-source

It would seem entirely logical from a software development perspective to treat I6/z-Machine as a branch and separate release of Inform 10+ and have the main branch be pure Glulx. Dropping down to C would seem to be a healthy way to handle special cases. they really are different “domains” of IF implementation and keeping them “married” might muddle things. But I’m just thinking out loud. I’d be of no help on any of the work you people are doing.

On Amanda’s question, whose irony can be parsed as “hitting the intrinsic limits of NL programming languages”, I suspect that we have already a sizeable bug report repository in the form of this very category (authoring/inform 7), many (solved) question are of the “how to render this or that in the NL of Inform 7 ?” type, and if Graham find the time for browsing these questions, I’m sure that the road for significant I7 NL improvement is opened…

Best regards from Italy,
dott. Piergiorgio.

So way back I talked about v10 rejecting page turned to by, which had been accepted by 9.3

A page is a kind of object.
Page-turning relates various pages to various pages.
The verb to turn to (he turns to, they turn to, he turned to, it is turned to, it is turning to) implies the page-turning relation.

[... then, within a phrase... ]

repeat with p running through pages turned to by page1:

So it turns out that given Fubar relates one thing to various things. both 9.3/6M62 and 10.1 will accept all of these verb definitions:

The verb to barate implies the fubar relation.
The verb to be carated implies the reversed fubar relation.
The verb to be darated by implies the reversed fubar relation.
The verb to be garated to by implies the reversed fubar relation.
The verb to carry to (he carries to, they carry to, he carried to, it is carried to, it is carrying to) implies the fubar relation.

and for both To verb to farate to (he farates to, they farate to, he farated to, it is farated to, it is farating to) implies the fubar relation. will fail compilation.

But despite accepting the verb definition, neither will compile:

repeat with c running through things carated by the player begin;
    say "carated [c].";
  end repeat;
 repeat with d running through things darated to by the player begin;
    say "darated [d].";
  end repeat;

In both, these work:

repeat with b running through things barated by the player begin;
    say "barated [b].";
  end repeat;
repeat with g running through things garated to by the player begin;
    say "garated [g].";
  end repeat;

But this works only in 6M62 and fails to compile in v10:

repeat with ct running through things carried to by the player begin;
    say "carried to [ct].";
  end repeat;

Anyway, this works in v10:

The verb to be turned to by implies the reversed page-turning relation.
[...]
repeat with p running through pages turned to by page1 begin;
    say "turn to by: [p].";
  end repeat;

I got bored, so I decided to look into compiling Inform with the Visual C++ command line tools (and aspirationally Visual Studio, though without specific support for literate programming it doesn’t add much value over command line).

The makefiles assume that if you’re building on Windows you’re using Clang and you’re in a POSIX environment, neither of which were true in my case. I made a .proj that spat out a bunch of .vcxprojs that know how to tangle before compiling and linking, but otherwise were vanilla Visual Studio projects with the appropriate flags (/D PLATFORM_WINDOWS=1 /D _WIN32_WINNT=0x0600 /std:c11). I did not migrate over indulgence levels, weaving, testing, or any other build targets. Almost all the problems I ran into were in Foundation, but there were two in Inform.

Foundation, Chapter 1, Windows Platform:

@@ -19,1 +19,1 @@ (very early code)
 #include <dirent.h>

The Windows SDK doesn’t come with a dirent.h, so I downloaded a polyfill by Toni Ronkko.

Foundation, Chapter 2, Streams:

@@ -49,3 +49,3 @@
 same, if you're porting this code, you may need to rewrite the macro with
 |...| in place of |args...| in the header, and then |__VA_ARGS__| in place
 of |args| in the definition: that being the modern way, apparently.

Visual C++ only supports the modern way, so I was very thankful to find this as it explained exactly what needed to be done not only here but several other places as well.

@@ -58,12 +58,12 @@
 = (early code)
-#define WRITE(args...) Writers::printf(OUT, args)
+#define WRITE(...) Writers::printf(OUT, __VA_ARGS__)

-#define PRINT(args...) Writers::printf(STDOUT, args)
+#define PRINT(...) Writers::printf(STDOUT, __VA_ARGS__)

-#define WRITE_TO(stream, args...) Writers::printf(stream, args)
+#define WRITE_TO(stream, ...) Writers::printf(stream, __VA_ARGS__)

-#define LOG(args...) Writers::printf(DL, args)
+#define LOG(...) Writers::printf(DL, __VA_ARGS__)

-#define LOGIF(aspect, args...) { \
-       if (Log::aspect_switched_on(aspect##_DA)) Writers::printf(DL, args); \
-}
+#define LOGIF(aspect, ...) { \
+       if (Log::aspect_switched_on(aspect##_DA)) Writers::printf(DL, __VA_ARGS__); \
+}

Here, Visual C++ complains about an unknown size. The other compilers assume bytes, but VC++ needs to be told that explicitly for safety:

@@ -779,7 +779,7 @@ <Ensure there is room to expand the escape sequence into>
                        int needed = offset + ((int) sizeof(text_stream)) + 32;
                        void *further_allocation = Memory::malloc(needed, STREAM_MREASON);
                        if (further_allocation == NULL) Errors::fatal("Out of memory");
-                       text_stream *continuation = (text_stream *) (further_allocation + offset);
+                       text_stream *continuation = (text_stream *) ((char *)further_allocation + offset);
                        Streams::initialise(continuation, FOR_CO_STRF);
                        continuation->write_to_memory = further_allocation;
                        continuation->chars_capacity = 2*stream->chars_capacity;

Foundation, Chapter 2, Methods:
Again, variadic macros have a specific required syntax for VC++:

@@ -49,10 +49,10 @@
 What these do is to use typedef to give the name |X_type| to the type of all
 functions sharing the method ID |X|.

-@d INT_METHOD_TYPE(id, args...)
-       typedef int (*id##_type)(args);
-@d VOID_METHOD_TYPE(id, args...)
-       typedef void (*id##_type)(args);
+@d INT_METHOD_TYPE(id, ...)
+       typedef int (*id##_type)(__VA_ARGS__);
+@d VOID_METHOD_TYPE(id, ...)
+       typedef void (*id##_type)(__VA_ARGS__);

Similar problems and solutions are at the method call macros on lines 122/126 and 144/147, not shown here.

Foundation, Chapter 3, Case-Insensitive Filenames:
While strcasecmp is a thing in the POSIX standard, for some reason Microsoft compilers prefer stricmp.

@@ -230,7 +230,7 @@ int CIFilingSystem::match_in_directory(void *vd, char *name, char *last_match)

        last_match[0] = 0;
        while ((dirp = readdir(d)) != NULL) {
-               if (strcasecmp(name, dirp->d_name) == 0) {
+               if (stricmp(name, dirp->d_name) == 0) {
                        rc++;
                        strcpy(last_match, dirp->d_name);
                }

Foundation, Chapter 5, HTML:

@@ -129,21 +129,21 @@
 @ We will open and close all HTML tags using the following macros, two
 of which are variadic and have to be written out the old-fashioned way:
 
 @d HTML_TAG(tag) HTML::tag(OUT, tag, NULL);
 @d HTML_OPEN(tag) HTML::open(OUT, tag, NULL, __FILE__, __LINE__);
 @d HTML_CLOSE(tag) HTML::close(OUT, tag, __FILE__, __LINE__);

 =
-#define HTML_TAG_WITH(tag, args...) { \
+#define HTML_TAG_WITH(tag, ...) { \
        TEMPORARY_TEXT(details) \
-       WRITE_TO(details, args); \
+       WRITE_TO(details, __VA_ARGS__); \
        HTML::tag(OUT, tag, details); \
        DISCARD_TEXT(details) \
 }

-#define HTML_OPEN_WITH(tag, args...) { \
+#define HTML_OPEN_WITH(tag, ...) { \
        TEMPORARY_TEXT(details) \
-       WRITE_TO(details, args); \
+       WRITE_TO(details, __VA_ARGS__); \
        HTML::open(OUT, tag, details, __FILE__, __LINE__); \
        DISCARD_TEXT(details) \
 }

At this point I was able to tangle and compile Inweb and Intest without issue. The linker was unhappy, though.

Foundation, Chapter 1, Windows Platform (again):

@@ -75,1 +75,1 @@ Environment variables.
 			if (SHGetFolderPathA(0, CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT, value) == 0)

I added Shell32.lib to the linker inputs and got myself some executables. There was one warning about stuffing a 64-bit pointer into a 32-bit long, but unless special compiler options are used (mainly security-related) it’s unlikely to cause a problem in practice. I made sure Intest ran and Inweb was able to tangle and weave itself and all was as it should be.

I moved on to Inform the Repo, spent far too much time on nonsense with MSBuild inline tasks, and got to the point where I could keep building.

Inform 7, Core Module, Chapter 3, Plugin Manager:
Fourth verse, same as the first.

@@ -193,13 +193,13 @@
 We must take care that the variables introduced in the macro body do not mask
 existing variables used in the arguments; the only way to do this is to give
 them implausible names.

-@d PLUGINS_CALL(code, args...) {
+@d PLUGINS_CALL(code, ...) {
        void *R_plugin_pointer_XYZZY; /* no argument can have this name */
        LOOP_OVER_LINKED_LIST(R_plugin_pointer_XYZZY, void, plugin_rulebooks[code]) {
                int (*R_plugin_rule_ZOOGE)() = (int (*)()) R_plugin_pointer_XYZZY; /* or this one */
-               int Q_plugin_return_PLUGH = (*R_plugin_rule_ZOOGE)(args); /* or this */
+               int Q_plugin_return_PLUGH = (*R_plugin_rule_ZOOGE)(__VA_ARGS__); /* or this */
                if (Q_plugin_return_PLUGH) return Q_plugin_return_PLUGH;
        }
        return FALSE;
 }

Inform 7, Problems, Chapter 2, Problems Level 2:
In Words, Chapter 3, Wordings, the author notes that for EMPTY_WORDING GCC requires a different pronunciation than usual for static initializers. VC++ additionally requires that special pronunciation here:

@@ -341,8 +341,8 @@ Appending source.
-wording appended_source = EMPTY_WORDING;
+wording appended_source = EMPTY_WORDING_INIT;
 void Problems::append_source(wording W) {
	 appended_source = W;
 }
 void Problems::transcribe_appended_source(void) {
	 if (Wordings::nonempty(appended_source))
		 ProblemBuffer::copy_source_reference(appended_source);
 }

With that, I was able to build everything Inform’s makefile defined as a “tool” as well, producing several thousand warnings about VC++ not understanding Clang’s warning suppression pragmas as well as executables that at the very least could print out their version number. I was also able to tangle the extensions, and convinced Inter to translate the kits, though Inbuild couldn’t find Inter to do so automatically. I’ll do further testing once there aren’t explosions directly outside my window.

Why post here instead of a GitHub pull request? Neither Dr. Nelson nor Mr. Kinder have shown any desire to support building in VC++, and these changes don’t have a positive impact on the compilers they do use. Adding support to Inweb for making MSBuild project files in addition to makefiles is a bit too aspirational, and my hand-rolled ones aren’t anywhere near ready to publish – especially without the ability to run any tests, produce the companion books or weave the documentation, or any of the other things the makefile does, not to mention being unhappy with pathnames with spaces – but all of that would need to be ready before such a request might be accepted. That said, I hope this post might help other people start playing with 10.1 if they have Visual Studio but not Clang and Make.

(Oh, and I transfer any copyright that might exist to Graham Nelson. Just in case these changes are better than I thought.)

6 Likes

It’s interesting to see that Visual Studio with Clang doesn’t require that many changes to get Inform to compile, I had wondered. The test harness will be more of a problem, though, as that really does rely on the presence of a Unix-y shell environment.

1 Like

I do disagree. I want to retain the ability to target the Z-machine until I run out of space. Extensions are generally quite compact – optimized in a way games typically aren’t – and I think it’s quite possible to make a small game targeting the Z-machine which uses a bunch of seemingly-complicated (but actually quite small, when compiled) extensions. After digging into the innards for a while, I don’t see much which varies between the two. I’d probably just build a Z-machine malloc. (Though for the thing I’m trying to implement right now, I actually just want a data stack: alloca.)

As has happened repeatedly before with Inform, the major problem users are facing is bad documentation: it’s not documented what’s supported and what isn’t. It never has been. “Literate programming” apparently didn’t help much with this. You can get away with very quirky behavior if it’s well-documented. (I still think Inform 7 needs a real reference manual, which it has never had. With the code open-sourced, it’s now possible for a third party to write it.)

Individual property declarations are highly useful and should be supported in Inter. “Property individual propname;” should be sufficient.

P.S. I’ve been working on a different programming project for the last month. I’m back for now.

4 Likes

I’m with Nathaniel on this one in many ways, but I want to lay out my full experience.

I like keeping games small when I can, and I also like having the programming exercise of keeping the blorb as small as possible for as long as possible. This isn’t just code-golf or whatever–it helps test runs go faster. I find my writing is divided pretty evenly between Z8 and Glulx. And yet I include a few headers per game – mostly of stubs, etc. But they are still headers. Stuff like my own stubs, or Undo Output Control.

On the other hand it looks like, with all the bells and whistles Inform has, this may not be practical. (I use 6G, and I can see building the same story.ni for later versions takes up progressively more disk space.) But I think for small-game jams, it’d be nice to keep the z-machine if possible. To me it encourages efficient coding and game design.

Yet I also have seen how projects independent of cutting-edge Inform, such as PunyInform, do the same thing, and since Dialog is maturing as a language but (IIRC) allows for lightweight builds where you can include extensions, thi may be something worth deprecating to allow the full features you want to present.

I can keep using 6G for z-machine projects until the cows come home. But I’d be sad to see a z-machine setting go, for Inform.

Now that Inform is open source, I’m interested in browsing to see how, after the player enters a command, the framework is called and the world model is updated.

But all the InWeb-generated documentation I’ve seen seems to be about the toolchain that compiles an Inform 7 story.

Where should I start looking?

1 Like

Sounds like you want to look at what’s now labeled as the if module.

1 Like

Or possible you are looking for the Action-processing rulebook in the Standard Rules:

Variables and Rulebooks

The basic game cycle is defined by the turn sequence rules, which begin with the “parse command rule” and the “generate action rule”. The core of the latter is the Action-processing rulebook.

1 Like

Note that the “if module”, which Mike Russo linked to, is part of the Inform 7 compiler.

If you’re interested in what happens during gameplay, you want the Standard Rules (I7 code) and the various Kits (I6 code).

Very true - I thought from the “now that Inform is open source” bit that he was looking for something other than the standard rules, but rereading the question that might well be wrong.

Good. Thank you.

This is the feature that interests me the most. Has the code been written in a way that would allow additional target languages to be added?

For example, could others create extensions to output Python, BBC BASIC, JavaScript etc. without needing to change the main source?

2 Likes

Yes, the new Inform7 code produces a “neutral”/abstract intermediate representation (IR) called Inter that can be compiled into or interpreted by many different frontends. See Using Inter for details.

3 Likes

Whose is the same frontend/backend technique used by the GCC compiler suite.

Inter can open new crosscompiling possibilities between IF languages and story file formats, for example, generating a glulx story file from TADS 3 or Alan 3 source, or generating TADS2/3 story file from Hugo or even Inform 6/7; of course, one can write front-end for general-purpose languages, esp. FORTRAN…

Best regards from Italy,
dott. Piergiorgio.

1 Like

I did wonder whether it would also be possible to pass other IF languages through Inter, but I would imagine that they have less in common with each other than general programming languages do.

From what little I have read about the new Inform system, there are five stages of compilation before Inter, which would need to be rewritten for whatever other IF language was used.

1 Like

Most of those five stages are specific to Inform, though. I6 only requires a single fairly-straightforward translation stage (called “assimilation”) to turn it into Inter.

Something like TADS or Dialog would of course take more, because Inter is specifically designed to be closely compatible with I6 (look at its list of primitives). But probably less than the full five needed for I7.

And as a side effect, since Inter is pretty close to I6 in choice of primitives and I6 is pretty close to the Z-machine in that same choice, it shouldn’t be too difficult for anything that currently compiles to the Z-machine (Dialog, ZILF) to compile to Inter instead, and then to Glulx or C or whatever. Inter does have some higher-level control constructs (like loops) but also supports labels and branches, so you could absolutely use it as an elaborate syntax for Z-machine assembly.

(Which does make me wonder about the feasibility of a Z-machine-to-Inter decompiler. Certainly easier than Z-machine-to-I6; I’d have to look at the standards again to figure out if any particular features would pose a problem.)

4 Likes

For the adventurous (and the impatient) among you, the latest state of the Windows Inform 7 IDE can be successfully built from source as described on GitHub. It’s pretty straightforward to integrate with the compiler too. My thanks to David Kinder for his help getting this to work.

5 Likes

I don’t think there’d be too much difference really.

The biggest complexity with decompiling Z-Code is the same one I tried to tackle in Glulxtoc - turning branches and jumps into loops and if/else/switch statements. Or you just don’t try, and output spaghetti code instead. In both cases Inter and I6 look roughly comparable to me.

The one difference would maybe be for laying out memory? I6 wants to fill the RAM with its own stuff, so you’d have to patch addresses to work around that. Maybe with Inter it’s able to have more control over the RAM, so you could just copy it more directly?

1 Like