Purpose: A guide for users of cblorb.
§3. Sentences in Inform source text such as:
Release along with public source text, cover art, and a website.
do in effect transmit instructions to cblorb
, but cblorb
doesn't read
them in this natural-language form. Instead, the ni
component of Inform 7
translates these instructions into a script for cblorb
to follow. This
script is called a "blurb".
"Blurb" is a mini-language for specifying how the materials in a work of IF should be packaged up for release. It was originally codified in 2001 as a standard way to describe how a blorb file should be put together, but it was extended in 2005 and again in 2008 so that it could also organise accompanying files released along with the blorb.
The original Blurb language was documented in chapter 43 of the DM4 (i.e., the {\it Inform Designer's Manual}, fourth edition, 2001); for clarity, we will call that language "Blurb 2001". Today's Blurb language is a little different. Some features of Blurb 2001 are deprecated and no longer used, while numerous other syntaxes are new. Because of this the DM4 specification is no longer useful, so we will give a full description below of Blurb as it currently stands.
ni
, the I7 compiler, as normal except that the
-release
command-line switch is specified.
ni
compiles the source text into I6 code. If Problems occur, ni
exits with a return code of 1, and the interface displays those, and
then stops the process.
ni
writes two additional files besides the I6
code it always writes:
Metadata.iFiction
, an iFiction record;
Release.blurb
, a blurb file of instructions for cblorb
to follow later.
ni
having returned 0 to indicate success, the interface next calls
the Inform 6 compiler (called, e.g., inform-6.31-biplatform
, but we'll
call it i6
here). The interface calls i6
as normal except that the S
and D
switches, for strict checking and for debugging, are off instead of
on. If ni
works properly then i6
should certainly not produce syntax
errors, though it will surely produce warnings; all the same it can fail if,
say, Z-machine memory limits are exceeded. The interface should deal with
such failures exactly as it would in a non-Release run.
i6
having returned 0 to indicate success, the interface next calls
cblorb
as follows. Let Path
be the path to the folder containing the Inform
project being released, which we'll call This.inform
. Then the interface
should call:
cblorb -platform "Path/This.inform/Release.blurb" "Path/This.inform/Build/output.gblorb"
-platform
should be one of -osx
, -windows
or -unix
. (The default
is -osx
.) The two filename arguments are the Blurb script for cblorb
to
follow, which was written by ni
at step 3, and the filename of the Blorb
file which it should write. Note that the interface should give this the
extension ".gblorb" if the Glulx setting is in force, and ".zblorb" if
the Z-machine.
cblorb
can produce error messages, and it returns
0 (success) or 1 (failure). But the interface doesn't actually need to look
at that, because cblorb
also produces a much fuller report in the form of
an HTML page to be displayed on the Problems tab. This is StatusCblorb.html
,
in the project's Build
folder. (This is a change made in 2010: in the past,
the interface simply chose between a generic success and failure page on the
basis of the return code.)
cblorb
succeeded) — to move the blorb somewhere sensible on disc, where the
user can see it. Leaving it where it is will not do — the user never looks
inside the Build project of a folder, which on Mac OS X, for instance, is
not even visible. To see what to do, the interface must look at the textual
output from cblorb
, printed to stdout
(of course the interface is free
to redirect this if it wants to). If cblorb
printed a line in the form:
Copy blorb to: [[...]]
Copy blorb to: [[/Users/gnelson/Examples/Bronze Materials/Release/Bronze.gblorb]]
cblorb
printed no such line, the interface should put up a Save As...
dialogue box, and invite the user to choose a destination.
Inform.app/Contents/Resources/Compilers/cBlorb
Its main usage is:
cblorb -platform [-options] blurbfile [blorbfile]
where -platform
should be one of -osx
, -windows
, -unix
. At present
the only practical difference this makes is that the Windows setting causes
cblorb
to use \| instead of
/ as a filename separator.
The blorbfile filename is optional since cblorb
does not always need to
make a blorb; that depends on the instructions handed to it in the blurbfile
.
§6. The other command-line options are:
-help
: prints summaries of command-line use and the Blurb language.
-trace
: mainly for debugging, but possibly also useful as a verbose mode.
-project Whatever.inform
: tells cblorb
to assume the usual settings for
this project. (That means the blurbfile is set to Whatever.inform/Release.blurb
and the blorbfile to Whatever.inform/Build/output.gblorb
.)
storyfile "/Users/gnelson/Examples/Zinc.inform/Build/output.ulx" include
ifiction "/Users/gnelson/Examples/Zinc.inform/Metadata.iFiction" include
These two lines tell cblorb
to include the story file and the iFiction
record respectively.
§8. A more ambitious Blorb can be made like so:
storyfile leafname "Audiophilia.gblorb"
storyfile "/Users/gnelson/Examples/Audiophilia.inform/Build/output.ulx" include
ifiction "/Users/gnelson/Examples/Audiophilia.inform/Metadata.iFiction" include
cover "/Users/gnelson/Examples/Audiophilia Materials/Cover.png"
picture 1 "/Users/gnelson/Examples/Audiophilia Materials/Cover.png"
sound 3 "/Users/gnelson/Examples/Audiophilia Materials/Sounds/Powermac.aiff"
sound 4 "/Users/gnelson/Examples/Audiophilia Materials/Sounds/Bach.ogg"
The cover image is included only once, but declaring it as picture 1 makes it
available to the story file for display internally as well as externally.
Resource ID 2, apparently skipped, is in fact the story file.
project folder "/Users/gnelson/Examples/Zinc.inform"
release to "/Users/gnelson/Examples/Zinc Materials/Release"
solution
This time no blorb file is made. The opening line tells cblorb
which Inform
project we're dealing with, allowing it to look at the various files inside --
its Skein, for instance, which is used to create a solution. The second line
tells cblorb
where to put all of its output — everything it makes. Only
the third line directly causes cblorb
to do anything.
project folder "/Users/gnelson/Examples/Audiophilia.inform"
release to "/Users/gnelson/Examples/Audiophilia Materials/Release"
placeholder [IFID] = "AD5648BA-18A2-48A6-9554-4F6C53484824"
placeholder [RELEASE] = "1"
placeholder [YEAR] = "2009"
placeholder [TITLE] = "Audiophilia"
placeholder [AUTHOR] = "Graham Nelson"
placeholder [BLURB] = "A test project for sound effect production."
template path "/Users/gnelson/Library/Inform/Templates"
css
website "Standard"
The first novelty here is the setting of placeholders. These are named pieces
of text which appear on the website being generated: where the text "[RELEASE]"
appears in the template, cblorb
writes the value we've set for it, in this
case "1". Some of these values look like numbers, but to cblorb
they all
hold text. A few placeholder names are reserved by cblorb
for its own use,
and it will produce errors if we try to set those, but none of those in
this example is reserved.
Template paths tell cblorb
where to find templates. Any number of these
can be set — including none at all, but if so then commands needing a
named template, like website
, can't be used. cblorb
looks for any
template it needs by trying each template path in turn (the earliest
defined having the highest priority). The blurb files produced by ni
in
its -release
mode containa chain of three template paths, for the
individual project folder, the user's library of installed templates, and
the built-in stock inside the Inform user interface application,
respectively.
The command css
tells cblorb
that it is allowed to use CSS styles to
make its web pages more appealing to look at: this results in generally
better HTML, easier to use in other contexts, too.
All of that set things up so that the website
command could be used,
which actually does something — it creates a website in the release-to
location, taking its design from the template named. If we were to add
any of these commands --
source public
solution public
ifiction public
-- then the website would be graced with these additions.
release to "Test Site"
placeholder [TITLE] = "Locksmith"
placeholder [AUTHOR] = "Emily Short"
placeholder [RUBRIC] = "Implicit handling of doors and..."
{\it and so on} template path "/Users/gnelson/Library/Inform/Templates"
css
release file "style.css" from "Extended"
release file "index.html" from "Extended"
release file "Extensions/Emily Short/Locksmith.i7x"
release source "Extensions/Emily Short/Locksmith.i7x" using "extsrc.html" from "Extended"
This time we're using a template called "Extended", and the script tells
cblorb
exactly what to do with it. The "release file... from..." command
tells cblorb
to extract the named file from this template and to copy it
into the release folder — if it's a ".html" file, placeholders are
substituted with their values. The simpler form, "release file ...", just
tells cblorb
to copy that actual file — here, it puts a copy of the
extension itself into the release folder. The final line produces a run
of pages, in all likelihood, for the source and documentation of the
extension, with the design drawn from "Extended" again.
("Extended" isn't supplied inside Inform; it's a template we're using to help generate the Inform website, rather than something meant for end users. There's nothing very special about it, in any case.)
Each command occupies one and only one line of text. (In Blorb 2001, the
now-deprecated palette
command could occupy multiple lines, but cblorb
will choke on such a usage.) Lines are permitted to be empty or to contain
only white space. Lines whose first non-white-space character is an
exclamation mark are treated as comments, that is, ignored. "White space"
means spaces and tab characters. An entirely empty blurb file, containing
nothing but white space, is perfectly legal though useless.
In the following description:
\def\bltoken#1{$\langle${\it #1}$\rangle$}%
\def\unsupported{\hfill{\it unsupported by cblorb
}}%
\bltoken{string} means any text within double-quotes, not
containing either double-quote or new-line characters, of up to 2048 bytes.
\bltoken{filename} means any double-quoted filename.
\bltoken{number} means a decimal number in the range 0 to 32767.
\bltoken{id} means either nothing at all, or a \bltoken{number},
or a sequence of up to 20 letters, digits or underscore characters _
.
\bltoken{dim} indicates screen dimensions, and must take the form
\bltoken{number}x
\bltoken{number}.
\bltoken{ratio} is a fraction in the form \bltoken{number}/\bltoken{number}. 0/0 is legal but otherwise both numbers must be positive.
\bltoken{colour} is a colour expressed as six hexadecimal digits,
as in some HTML tags: for instance F5DEB3
is the colour of wheat, with red
value F5
(on a scale 00
, none, to FF
, full), green value DE
and blue
value B3
. Hexadecimal digits may be given in either upper or lower case.
§13. The full set of commands is as follows. First, core commands for making a blorb:
author
\bltoken{string}
Adds this author name to the file.
copyright
\bltoken{string}
Adds this copyright declaration to the blorb file. It would normally consist of
short text such as "(c) J. Mango Pineapple 2007" rather than a lengthy legal
discourse.
release
\bltoken{number}
Gives this release number to the blorb file.
auxiliary
\bltoken{filename} \bltoken{string}
Tells us that an auxiliary file — for instance, a PDF manual — is associated
with the release but will not be embedded directly into the blorb file. For
instance,
auxiliary "map.png" "Black Pete's treasure map"
The string should be a textual description of the contents. Every auxiliary
file should have a filename including an extension usefully describing its
format, as in ".png": if there is no extension, then the auxiliary resource
is assumed to be a mini-website housed in a subfolder with this name.
ifiction| \bltoken{filename} |include
The file should be a valid iFiction record for the work. This is an XML file
specified in the Treaty of Babel, a cross-IF-system standard for specifying
bibliographic data; it will be embedded into the blorb.
storyfile
\bltoken{filename} \unsupported storyfile| \bltoken{filename} |include
Specifies the filename of the story file which these resources are being
attached to. Blorb 2001 allowed for blorbs to be made which held everything
to do with the release {\it except} the story file; that way a release
might consist of one story file plus one Blorb file containing its pictures
and sounds. The Blorb file would then contain a note of the release number,
serial code and checksum of the associated story file so that an
interpreter can try to match up the two files at run-time. If the include
option is used, however, the entire story file is embedded within the Blorb
file, so that game and resources are all bound up in one single file.
cblorb
always does this, and does not support storyfile
without
include
.
§14. Second, now-deprecated commands describing our ideal screen display:
palette 16 bit
\unsupported palette 32 bit
\unsupported palette {| \bltoken{colour-1} É \bltoken{colour-N} |}
\unsupported
Blorb allows designers to signal to the interpreter that a particular
colour-scheme is in use. The first two options simply suggest that the
pictures are best displayed using at least 16-bit, or 32-bit, colours. The
third option specifies colours used in the pictures in terms of
red/green/blue levels, and the braces allow the sequence of colours to
continue over many lines. At least one and at most 256 colours may be
defined in this way. This is only a "clue" to the interpreter; see the
Blorb specification for details.
resolution
\bltoken{dim} \unsupported resolution| \bltoken{dim} |min
\bltoken{dim} \unsupported resolution| \bltoken{dim} |max
\bltoken{dim} \unsupported resolution| \bltoken{dim} |min| \bltoken{dim} |max
\bltoken{dim} \unsupported
Allows the designer to signal a preferred screen size, in real pixels, in
case the interpreter should have any choice over this. The minimum and
maximum values are the extreme values at which the designer thinks the game
will be playable: they're optional, the default values being $0\times 0$ and
$\infty\times\infty$.
§15. Third, commands for adding audiovisual resources:
sound
\bltoken{id} \bltoken{filename} sound| \bltoken{id} \bltoken{filename} |repeat
\bltoken{number} \unsupported sound| \bltoken{id} \bltoken{filename} |repeat forever
\unsupported sound| \bltoken{id} \bltoken{filename} |music
\unsupported sound| \bltoken{id} \bltoken{filename} |song
\unsupported
Tells us to take a sound sample from the named file and make it the sound
effect with the given number. Most forms of sound
are now deprecated:
repeat information (the number of repeats to be played) is meaningful
only with Z-machine version 3 story files using sound effects, and Inform 7
does not generate those; the music
and song
keywords specify unusual
sound formats. Nowadays the straight sound
command should always
be used regardless of format.
picture
\bltoken{id} \bltoken{filename} picture| \bltoken{id} \bltoken{filename} |scale
\bltoken{ratio} \unsupported picture| \bltoken{id} \bltoken{filename} |scale min
\bltoken{ratio} \unsupported picture| \bltoken{id} \bltoken{filename} |scale| \bltoken{ratio} |min
\bltoken{ratio} \unsupported
(and so on) is a similar command for images. In 2001, the image file was required
to be a PNG, but it can now alternatively be a JPEG.
Optionally, the designer can specify a scale factor at which the interpreter will display the image — or, alternatively, a range of acceptable scale factors, from which the interpreter may choose its own scale factor. (By default an image is not scaleable and an interpreter must display it pixel-for-pixel.) There are three optional scale factors given: the preferred scale factor, the minimum and the maximum allowed. The minimum and maximum each default to the preferred value if not given, and the default preferred scale factor is 1. Scale factors are expressed as fractions: so for instance,
picture "flag/png" scale 3/1
means "always display three times its normal size", whereas
picture "backdrop/png" scale min 1/10 max 8/1
means"you can display this anywhere between one tenth normal size and
eight times normal size, but if possible it ought to be just its normal
size".
cblorb
does not support any of the scaled forms of picture
. As with
the exotic forms of sound
, they now seem pass\'e. We no longer need to
worry too much about the size of the blorb file, nor about screens with
very low resolution; an iPhone today has a screen resolution close to that
of a typical desktop of 2001.
cover
\bltoken{filename}
specifies that this is the cover art; it must also be declared with a
picture
command in the usual way, and must have picture ID 1.
§16. Three commands help us to specify locations.
project folder
\bltoken{filename}
Tells cblorb
to look for associated resources, such as the Skein file,
within this Inform project.
release to
\bltoken{filename}
Tells cblorb
that all of its output should go into this folder. (Well,
except that the blorb file itself will be written to the location specified
in the command line arguments, but see the description above of how cblorb
then contrives to move it.) The folder must already exist, and cblorb
won't create it. Under some circumstances Inform will seem to be creating
the release folder if it doesn't already exist, but that's always the work
of ni
, not cblorb
.
template path
\bltoken{filename}
Sets a search path for templates — a folder in which to look for them. There
can be any number of template paths set, and cblorb
checks them in order
of declaration (i.e., most important first).
No explicit single command causes a Blorb file to be generated; it will be made automatically if one of the above commands to include the story file, pictures, etc., is present in the script, and otherwise not generated.
solution
solution public
causes a solution file to be generated in the release folder. The mechanism
for this is described in {\it Writing with Inform}. The difference between
the two commands affects only a website also being made, if one is: a
public solution will be included in its links, thus being made available
to the public who read the website.
ifiction
ifiction public
is similar, but for the iFiction record of the project.
source
source public
is again similar, but here there's a twist. If the source is public, then
cblorb
doesn't just include it on a website: it generates multiple HTML
pages to show it off in HTML form, as well as including the plain text
original.
Miscellaneous files can be released like so:
release file
\bltoken{filename}
Here cblorb
acts as no more than a file-copy utility; a verbatim copy of
the named file is placed in the release folder.
§18. Finally we come to web pages.
css
enables the use of CSS-defined styles within the HTML generated by cblorb
.
This has an especially marked effect when cblorb
is generating HTML
versions of Inform source text, and is {\it a good thing}. Unless there is
reason not to, every blurb script generating websites ought to contain
this command.
release file| \bltoken{filename} |from
\bltoken{template}
causes the named file to be found from the given template. If it can't be
found in that template, cblorb
tries to find it from a template called
"Standard". If it isn't there either, or cblorb
can't find any template
called "Standard" in any of its template paths (see above), then an
error message is produced. But if all goes well the file is copied into
the release folder. If it has the file extension ".html" (in lower case,
and using that exact form, i.e., not ".HTM" or some other variation)
then any placeholders in the file will be expanded with their values.
A few reserved placeholders have special effects, causing cblorb
to
expand interesting text in their places — see {\it Writing with Inform}
for more on this.
release source| \bltoken{filename} using \bltoken{filename} |from
\bltoken{template}
makes cblorb
convert the Inform source text in the first filename into a
suite of web pages using the style of the given file from the given template.
website
\bltoken{template}
saves the best until last: it makes a complete website for an Inform project,
using the named template. This means that the CSS file is copied into place
(assuming css
is used), the "index.html" is released from the template,
the source of the project is run through release source
using "source.html"
from the template (assuming source public
is used), and any extra files
specified in the template's "(extras.txt)" are released as well. See
{\it Writing with Inform} for more.
The encoding part is taken care of by:
base64| \bltoken{filename} |to
\bltoken{filename}
This performs an RFC 1113-standard encoding on the binary file in (almost
always our story file) into a textual base-64 file out. The file is topped
and tailed with the text in placeholders [BASESIXTYFOURTOP]
and [BASESIXTYFOURTAIL]
,
allowing Javascript wrapper code to surround the encoded data.
The interpreter itself is copied into place in the Release folder in a process rather like the construction of a website from a template. The necessary blurb command is:
interpreter
\bltoken{interpreter-name} \bltoken{vm-letter}
Interpreter names are like template names; Inform often uses "Parchment".
The VM letter should be "g" if we need this to handle a Glulx story file
(blorbed up), or "z" if we need it to handle a Z-machine story file.
(This needs to be said because Inform doesn't have a way of knowing which
formats a given interpreter can handle; so it has to leave checking to
cblorb
to do. Thus, if an Inform user tries to release a Z-machine-only
interpreter with a Glulx story file, it's cblorb
which issues the error,
not Inform itself.)
status
\bltoken{template} \bltoken{filename} status alternative
\bltoken{link to Inform documentation} status instruction
\bltoken{link to Inform source text}
The first simply requests the page to be made. It's made from a single
template file, but in exactly the same way that website pages are generated
from website templates — that is, placeholders are expanded. The second
filename is where to write the result.
The other two commands allow Inform to insert information which cblorb
otherwise has no access to: options for fancy release tricks not currently
being used (with links to the documentation on them), and links to source
text "Release along with..." sentences.
Purpose: To parse command-line arguments and take the necessary steps to obey them.
Definitions:
¶1. We will need the following:
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #include <ctype.h>
¶2. We identify which platform we're running on thus:
define OSX_PLATFORM 1 define WINDOWS_PLATFORM 2 define UNIX_PLATFORM 3
define MAX_FILENAME_LENGTH 10240 total length of pathname including leaf and extension define MAX_EXTENSION_LENGTH 32 extension part of filename, for auxiliary files define MAX_VAR_NAME_LENGTH 32 length of name of placeholder variable like "[AUTHOR]" define MAX_TEXT_FILE_LINE_LENGTH 51200 for any single line in the project's source text define MAX_SOURCE_TEXT_LINES 2000000000; enough for 300 copies of the Linux kernel source — plenty!
define VERSION "cBlorb 1.2" define TRUE 1 define FALSE 0
char SEP_CHAR = '/'; local file-system filename separator
char *FONT_TAG = "size=2"; contents of a <font>
tag
char *JAVASCRIPT_PRELUDE = "javascript:window.Project."; calling prefix
int escape_openUrl = FALSE, escape_fileUrl = FALSE;
int reverse_slash_openUrl = FALSE, reverse_slash_fileUrl = FALSE;
int trace_mode = FALSE; print diagnostics tostdout
while running? int error_count = 0; number of error messages produced so far int current_year_AD = 0; e.g., 2008 int blorb_file_size = 0; size in bytes of the blorb file written int no_pictures_included = 0; number of picture resources included in the blorb int no_sounds_included = 0; number of sound resources included in the blorb int HTML_pages_created = 0; number of pages created in the website, if any int source_HTML_pages_created = 0; number of those holding source int sound_resource_num = 3; current sound resource number we're working on int picture_resource_num = 1; current picture resource number we're working on int use_css_code_styles = FALSE; use<span class="X">
markings when setting code char project_folder[MAX_FILENAME_LENGTH]; pathname of I7 project folder, if any char release_folder[MAX_FILENAME_LENGTH]; pathname of folder for website to write, if any char status_template[MAX_FILENAME_LENGTH]; filename of report HTML page template, if any char status_file[MAX_FILENAME_LENGTH]; filename of report HTML page to write, if any int cover_exists = FALSE; an image is specified as cover art int default_cover_used = FALSE; but it's only the default supplied by Inform int cover_is_in_JPEG_format = TRUE; as opposed toPNG
format
That's a little over-simplified, though, because it also produces auxiliary outputs along the way, in the course of parsing the blurb file. The blorb file is only the main output — there might also be a web page and a solution file, for instance.
int main(int argc, char *argv[]) { int platform, produce_help; char blurb_filename[MAX_FILENAME_LENGTH]; char blorb_filename[MAX_FILENAME_LENGTH]; <Make the default settings 7.1>; <Parse command-line arguments 7.2>; start_memory(); establish_time(); initialise_placeholders(); print_banner(); if (produce_help) { <Produce help 7.3>; return 0; } parse_blurb_file(blurb_filename); write_blorb_file(blorb_filename); create_requested_material(); print_report(); free_memory(); if (error_count > 0) return 1; return 0; }
The function main is used in 3/rel (§12).
§7.1.
<Make the default settings 7.1> =
platform = OSX_PLATFORM; produce_help = FALSE; release_folder[0] = 0; project_folder[0] = 0; status_file[0] = 0; status_template[0] = 0; strcpy(blurb_filename, "Release.blurb"); strcpy(blorb_filename, "story.zblorb");
This code is used in §7.
§7.2.
<Parse command-line arguments 7.2> =
int arg, names; for (arg = 1, names = 0; arg < argc; arg++) { char *p = argv[arg]; if (cblorb_strlen(p) >= MAX_FILENAME_LENGTH) { fprintf(stderr, "cblorb: command line argument %d too long\n", arg+1); return 1; } <Parse an individual command-line argument 7.2.1>; } <Set platform-dependent HTML and Javascript variables 7.2.2>; if (project_folder[0] != 0) { if (names > 0) <Command line syntax error 7.2.3>; sprintf(blurb_filename, "%s%cRelease.blurb", project_folder, SEP_CHAR); sprintf(blorb_filename, "%s%cBuild%coutput.zblorb", project_folder, SEP_CHAR, SEP_CHAR); } if (trace_mode) printf("! Blurb in: <%s>\n! Blorb out: <%s>\n", blurb_filename, blorb_filename);
This code is used in §7.
§7.2.1.
<Parse an individual command-line argument 7.2.1> =
if (strcmp(p, "-help") == 0) { produce_help = TRUE; continue; } if (strcmp(p, "-osx") == 0) { platform = OSX_PLATFORM; continue; } if (strcmp(p, "-windows") == 0) { platform = WINDOWS_PLATFORM; continue; } if (strcmp(p, "-unix") == 0) { platform = UNIX_PLATFORM; continue; } if (strcmp(p, "-trace") == 0) { trace_mode = TRUE; continue; } if (strcmp(p, "-project") == 0) { arg++; if (arg == argc) <Command line syntax error 7.2.3>; strcpy(project_folder, argv[arg]); continue; } if (p[0] == '-') <Command line syntax error 7.2.3>; names++; switch (names) { case 1: strcpy(blurb_filename, p); break; case 2: strcpy(blorb_filename, p); break; default: <Command line syntax error 7.2.3>; }
This code is used in §7.2.
cblorb
generates quite a variety of HTML, for instance to create websites,
but the tricky points below affect only one special page not browsed by
the general public: the results page usually called StatusCblorb.html
(though this depends on how the status
command is used in the blurb).
The results page is intended only for viewing within the Inform user
interface, and it expects to have two Javascript functions available,
openUrl
and fileUrl
. Because the object structure has needed to be
different for the Windows and OS X user interface implementations of
Javascript, we abstract the prefix for these function calls into the
JAVASCRIPT_PRELUDE
. Thus
<a href="***openUrl">...</a>
causes a link, when clicked, to call the openUrl
function, where ***
is the prelude; similarly for fileUrl
. The first opens a URL in the local
operating system's default web browser, the second opens a file (identified
by a file:...
URL) in the local operating system. These two URLs may
need treatment to handle special characters:
%2520
, which
within a Javascript string literal produces %20
, the standard way to
represent a space in a web URL;
<Set platform-dependent HTML and Javascript variables 7.2.2> =
if (platform == OSX_PLATFORM) { FONT_TAG = "face=\"lucida grande,geneva,arial,tahoma,verdana,helvetica,helv\" size=2"; escape_openUrl = TRUE; OS X requiresopenUrl
to escape, andfileUrl
not to } if (platform == WINDOWS_PLATFORM) { SEP_CHAR = '\\'; JAVASCRIPT_PRELUDE = "javascript:external.Project."; reverse_slash_openUrl = TRUE; reverse_slash_fileUrl = TRUE; }
This code is used in §7.2.
printf("This is cblorb, a component of Inform 7 for packaging up IF materials.\n\n"); <Show command line usage 7.3.1>; summarise_blurb();
This code is used in §7.
§7.2.3.
<Command line syntax error 7.2.3> =
<Show command line usage 7.3.1>;
return 1;
This code is used in §7.2, §7.2.1 (three times).
§7.3.1.
<Show command line usage 7.3.1> =
printf("usage: cblorb -platform [-options] [blurbfile [blorbfile]]\n\n"); printf(" Where -platform should be -osx (default), -windows, or -unix\n"); printf(" As an alternative to giving filenames for the blurb and blorb,\n"); printf(" -project Whatever.inform\n"); printf(" sets blurbfile and blorbfile names to the natural choices.\n"); printf(" The other possible options are:\n"); printf(" -help ... print this usage summary\n"); printf(" -trace ... print diagnostic information during run\n");
This code is used in §7.3, §7.2.3.
time_t the_present; struct tm *here_and_now; void establish_time(void) { the_present = time(NULL); here_and_now = localtime(&the_present); }
The function establish_time is used in §7.
void initialise_time_variables(void) { char datestamp[100], infocom[100], timestamp[100]; char *weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; set_placeholder_to_number("YEAR", here_and_now->tm_year+1900); sprintf(datestamp, "%s %d %s %d", weekdays[here_and_now->tm_wday], here_and_now->tm_mday, months[here_and_now->tm_mon], here_and_now->tm_year+1900); sprintf(infocom, "%02d%02d%02d", here_and_now->tm_year-100, here_and_now->tm_mon + 1, here_and_now->tm_mday); sprintf(timestamp, "%02d:%02d.%02d", here_and_now->tm_hour, here_and_now->tm_min, here_and_now->tm_sec); set_placeholder_to("DATESTAMP", datestamp, 0); set_placeholder_to("INFOCOMDATESTAMP", infocom, 0); set_placeholder_to("TIMESTAMP", timestamp, 0); }
The function initialise_time_variables is used in 3/place (§2).
void print_banner(void) { printf("! %s [executing on %s at %s]\n", VERSION, read_placeholder("DATESTAMP"), read_placeholder("TIMESTAMP")); printf("! The blorb spell (safely protect a small object "); printf("as though in a strong box).\n"); }
The function print_banner is used in §7.
void print_report(void) { if (error_count > 0) printf("! Completed: %d error(s)\n", error_count); <Set a whole pile of placeholders which will be needed to generate the status page 11.1>; if (status_template[0]) web_copy(status_template, status_file); }
The function print_report is used in §7, 1/text (§5).
<Set a whole pile of placeholders which will be needed to generate the status page 11.1> =
if (error_count > 0) { set_placeholder_to("CBLORBSTATUS", "Failed", 0); set_placeholder_to("CBLORBSTATUSIMAGE", "inform:/outcome_images/cblorb_failed.png", 0); set_placeholder_to("CBLORBSTATUSTEXT", "Inform translated your source text as usual, to manufacture a 'story " "file': all of that worked fine. But the Release then went wrong, for " "the following reason:<p><ul>[CBLORBERRORS]</ul>", 0 ); } else { set_placeholder_to("CBLORBERRORS", "No problems occurred", 0); set_placeholder_to("CBLORBSTATUS", "Succeeded", 0); set_placeholder_to("CBLORBSTATUSIMAGE", "file://[SMALLCOVER]", 0); set_placeholder_to("CBLORBSTATUSTEXT", "All went well. I've put the released material into the 'Release' subfolder " "of the Materials folder for the project: you can take a look with " "the menu option <b>Release > Open Materials Folder</b> or by clicking " "the blue folders above.<p>" "Releases can range in size from a single blorb file to a medium-sized website. " "Here's what we currently have:<p>", 0 ); report_requested_material("CBLORBSTATUSTEXT"); } if (blorb_file_size > 0) { set_placeholder_to_number("BLORBFILESIZE", blorb_file_size/1024); set_placeholder_to_number("BLORBFILEPICTURES", no_pictures_included); set_placeholder_to_number("BLORBFILESOUNDS", no_sounds_included); printf("! Completed: wrote blorb file of size %d bytes ", blorb_file_size); printf("(%d picture(s), %d sound(s))\n", no_pictures_included, no_sounds_included); } else { set_placeholder_to_number("BLORBFILESIZE", 0); set_placeholder_to_number("BLORBFILEPICTURES", 0); set_placeholder_to_number("BLORBFILESOUNDS", 0); printf("! Completed: no blorb output requested\n"); }
This code is used in §11.
Purpose: To allocate memory suitable for the dynamic creation of objects of different sizes, placing some larger objects automatically into doubly linked lists and assigning each a unique allocation ID number.
Definitions:
It allocates memory as needed to store the numerous objects of different sizes, all typedef'd structs. There's no garbage collection because nothing is ever destroyed. Each type has its own doubly-linked list, and in each type the objects created are given unique IDs (within that type) counting upwards from 0.
Smaller objects are stored in arrays, and their structure declarations do not use the following macro.
define MEMORY_MANAGEMENT int allocation_id; Numbered from 0 upwards in creation order void *next_structure; Next object in double-linked list void *prev_structure; Previous object in double-linked list
define auxiliary_file_MT 0
define skein_node_MT 1
define chunk_metadata_MT 2
define placeholder_MT 3
define heading_MT 4
define table_MT 5
define segment_MT 6
define request_MT 7
define template_MT 8
define template_path_MT 9
define rdes_record_MT 10
define NO_MEMORY_TYPES 11 must be 1 more than the highest _MT
constant above
typedef struct allocation_status_structure {
actually needed for allocation purposes:
int objects_allocated; total number of objects (or arrays) ever allocated
void *first_in_memory; head of doubly linked list
void *last_in_memory; tail of doubly linked list
used only to provide statistics for the debugging log:
char *name_of_type; e.g., "lexicon_entry_MT"
int bytes_allocated; total allocation for this type of object, not counting overhead
int objects_count; total number currently in existence (i.e., undeleted)
int no_allocated_together; number of objects in each array of this type of object
} allocation_status_structure;
The structure allocation_status_structure is private to this section.
allocation_status_structure alloc_status[NO_MEMORY_TYPES]; void start_memory(void) { int i; for (i=0; i<NO_MEMORY_TYPES; i++) { alloc_status[i].first_in_memory = NULL; alloc_status[i].last_in_memory = NULL; alloc_status[i].objects_allocated = 0; alloc_status[i].objects_count = 0; alloc_status[i].bytes_allocated = 0; alloc_status[i].no_allocated_together = 1; alloc_status[i].name_of_type = "unused"; } }
The function start_memory is used in 1/main (§7).
define SAFETY_MARGIN 64 define BLANK_END_SIZE 128
After MAX_BLOCKS_ALLOWED
blocks, we throw in the towel: we must have
fallen into an endless loop which creates endless new objects somewhere.
(If this ever happens, it would be a bug: the point of this mechanism is to
be able to recover. Without this safety measure, OS X in particular would
grind slowly to a halt, never refusing a malloc
, until the user was
unable to get the GUI responsive enough to kill the process.)
define MAX_BLOCKS_ALLOWED 15000 define MEMORY_GRANULARITY 100*1024*4 which must be divisible by 1024
int no_blocks_allocated = 0; int total_objects_allocated = 0; a much larger number, used only for the debugging log
typedef struct memblock_header { int block_number; struct memblock_header *next; char *the_memory; } memblock_header; memblock_header *first_memblock_header = NULL; head of list of memory blocks memblock_header *current_memblock_header = NULL; tail of list of memory blocks int used_in_current_memblock = 0; number of bytes so far used in the tail memory block
The structure memblock_header is private to this section.
§10. The actual allocation and deallocation is performed by the following pair of routines.
void allocate_another_block(void) { unsigned char *cp; memblock_header *mh; <Allocate and zero out a block of memory, making cp point to it 10.1>; mh = (memblock_header *) cp; used_in_current_memblock = sizeof(memblock_header) + SAFETY_MARGIN; mh->the_memory = (void *) (cp + used_in_current_memblock); <Add new block to the tail of the list of memory blocks 10.2>; }
The function allocate_another_block is used in §15.1.
<Allocate and zero out a block of memory, making cp point to it 10.1> =
int i; if (no_blocks_allocated++ >= MAX_BLOCKS_ALLOWED) fatal( "the memory manager has halted cblorb, which seems to be generating " "endless structures. Presumably it is trapped in a loop"); check_memory_integrity(); cp = (unsigned char *) (malloc(MEMORY_GRANULARITY)); if (cp == NULL) fatal("Run out of memory: malloc failed"); for (i=0; i<MEMORY_GRANULARITY; i++) cp[i] = 0;
This code is used in §10.
§10.2. As can be seen, memory block numbers count upwards from 0 in order of their allocation.
<Add new block to the tail of the list of memory blocks 10.2> =
if (current_memblock_header == NULL) { mh->block_number = 0; first_memblock_header = mh; } else { mh->block_number = current_memblock_header->block_number + 1; current_memblock_header->next = mh; } current_memblock_header = mh;
This code is used in §10.
void free_memory(void) { memblock_header *mh = first_memblock_header; while (mh != NULL) { memblock_header *next_mh = mh->next; void *p = (void *) mh; free(p); mh = next_mh; } }
The function free_memory is used in 1/main (§7).
define INTEGRITY_NUMBER 0x12345678 a value unlikely to be in memory just by chance
typedef struct memory_frame {
int integrity_check; this should always contain the INTEGRITY_NUMBER
struct memory_frame *next_frame; next frame in the list of memory frames
int mem_type; type of object stored in this frame
int allocation_id; allocation ID number of object stored in this frame
} memory_frame;
The structure memory_frame is private to this section.
memory_frame *first_memory_frame = NULL; earliest memory frame ever allocated memory_frame *last_memory_frame = NULL; most recent memory frame allocated
int calls_to_cmi = 0; void check_memory_integrity(void) { int c; memory_frame *mf; c = calls_to_cmi++; if (!((c<10) || (c == 100) || (c == 1000) || (c == 10000))) return; for (c = 0, mf = first_memory_frame; mf; c++, mf = mf->next_frame) if (mf->integrity_check != INTEGRITY_NUMBER) fatal("Memory manager failed integrity check"); } void debug_memory_frames(int from, int to) { int c; memory_frame *mf; for (c = 0, mf = first_memory_frame; (mf) && (c <= to); c++, mf = mf->next_frame) if (c >= from) { char *desc = "corrupt"; if (mf->integrity_check == INTEGRITY_NUMBER) desc = alloc_status[mf->mem_type].name_of_type; } }
The function check_memory_integrity is used in §10.1.
The function debug_memory_frames appears nowhere else.
void *allocate_mem(int mem_type, int extent) {
unsigned char *cp;
memory_frame *mf;
int bytes_free_in_current_memblock, extent_without_overheads = extent;
extent += sizeof(memory_frame); each allocation is preceded by a memory frame
extent += SAFETY_MARGIN; each allocation is followed by SAFETY_MARGIN
null bytes
<Ensure that the current memory block has room for this many bytes 15.1>;
cp = ((unsigned char *) (current_memblock_header->the_memory)) + used_in_current_memblock;
used_in_current_memblock += extent;
mf = (memory_frame *) cp; the new memory frame,
cp = cp + sizeof(memory_frame); following which is the actual allocated data
mf->integrity_check = INTEGRITY_NUMBER;
mf->allocation_id = alloc_status[mem_type].objects_allocated;
mf->mem_type = mem_type;
<Add the new memory frame to the big linked list of all frames 15.2>;
<Update the allocation status for this type of object 15.3>;
total_objects_allocated++;
return (void *) cp;
}
The function allocate_mem is used in §18.
<Ensure that the current memory block has room for this many bytes 15.1> =
if (current_memblock_header == NULL) allocate_another_block(); bytes_free_in_current_memblock = MEMORY_GRANULARITY - (used_in_current_memblock + extent); if (bytes_free_in_current_memblock < BLANK_END_SIZE) { allocate_another_block(); if (extent+BLANK_END_SIZE >= MEMORY_GRANULARITY) fatal("Memory manager failed because granularity too low"); }
This code is used in §15.
§15.2. New memory frames are added to the tail of the list:
<Add the new memory frame to the big linked list of all frames 15.2> =
mf->next_frame = NULL; if (first_memory_frame == NULL) first_memory_frame = mf; else last_memory_frame->next_frame = mf; last_memory_frame = mf;
This code is used in §15.
§15.3. See the definition of alloc_status
above.
<Update the allocation status for this type of object 15.3> =
if (alloc_status[mem_type].first_in_memory == NULL) alloc_status[mem_type].first_in_memory = (void *) cp; alloc_status[mem_type].last_in_memory = (void *) cp; alloc_status[mem_type].objects_allocated++; alloc_status[mem_type].bytes_allocated += extent_without_overheads;
This code is used in §15.
Note that inweb
allows multi-line macro definitions without backslashes
to continue them, unlike ordinary C. Otherwise these are "standard"
macros, though this was my first brush with the ##
concatenation
operator: basically CREATE(thing)
expands into (allocate_thing())
because of the ##
. (See Kernighan and Ritchie, section 4.11.2.)
define CREATE(type_name) (allocate_##type_name()) define CREATE_BEFORE(existing, type_name) (allocate_##type_name##_before(existing)) define DESTROY(this, type_name) (deallocate_##type_name(this)) define FIRST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_MT].first_in_memory) define LAST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_MT].last_in_memory) define NEXT_OBJECT(this, type_name) ((type_name *) (this->next_structure)) define PREV_OBJECT(this, type_name) ((type_name *) (this->prev_structure)) define NUMBER_CREATED(type_name) (alloc_status[type_name##_MT].objects_count)
define LOOP_OVER(var, type_name) for (var=FIRST_OBJECT(type_name); var != NULL; var = NEXT_OBJECT(var, type_name)) define LOOP_BACKWARDS_OVER(var, type_name) for (var=LAST_OBJECT(type_name); var != NULL; var = PREV_OBJECT(var, type_name))
Quaintly, #type_name
expands into the value of type_name
put within
double-quotes.
define NEW_OBJECT(type_name) ((type_name *) allocate_mem(type_name##_MT, sizeof(type_name))) define ALLOCATE_INDIVIDUALLY(type_name) type_name *allocate_##type_name(void) { alloc_status[type_name##_MT].name_of_type = #type_name; type_name *prev_obj = LAST_OBJECT(type_name); type_name *new_obj = NEW_OBJECT(type_name); new_obj->allocation_id = alloc_status[type_name##_MT].objects_allocated-1; new_obj->next_structure = NULL; if (prev_obj != NULL) prev_obj->next_structure = (void *) new_obj; new_obj->prev_structure = prev_obj; alloc_status[type_name##_MT].objects_count++; return new_obj; } void deallocate_##type_name(type_name *kill_me) { type_name *prev_obj = PREV_OBJECT(kill_me, type_name); type_name *next_obj = NEXT_OBJECT(kill_me, type_name); if (prev_obj == NULL) { alloc_status[type_name##_MT].first_in_memory = next_obj; } else { prev_obj->next_structure = next_obj; } if (next_obj == NULL) { alloc_status[type_name##_MT].last_in_memory = prev_obj; } else { next_obj->prev_structure = prev_obj; } alloc_status[type_name##_MT].objects_count--; } type_name *allocate_##type_name##_before(type_name *existing) { type_name *new_obj = allocate_##type_name(); deallocate_##type_name(new_obj); new_obj->prev_structure = existing->prev_structure; if (existing->prev_structure != NULL) ((type_name *) existing->prev_structure)->next_structure = new_obj; else alloc_status[type_name##_MT].first_in_memory = (void *) new_obj; new_obj->next_structure = existing; existing->prev_structure = new_obj; alloc_status[type_name##_MT].objects_count++; return new_obj; }
define ALLOCATE_IN_ARRAYS(type_name, NO_TO_ALLOCATE_TOGETHER) typedef struct type_name##_array { int used; struct type_name array[NO_TO_ALLOCATE_TOGETHER]; MEMORY_MANAGEMENT } type_name##_array; ALLOCATE_INDIVIDUALLY(type_name##_array) type_name##_array *next_##type_name##_array = NULL; struct type_name *allocate_##type_name(void) { if ((next_##type_name##_array == NULL) || (next_##type_name##_array->used >= NO_TO_ALLOCATE_TOGETHER)) { alloc_status[type_name##_array_MT].no_allocated_together = NO_TO_ALLOCATE_TOGETHER; next_##type_name##_array = allocate_##type_name##_array(); next_##type_name##_array->used = 0; } return &(next_##type_name##_array->array[ next_##type_name##_array->used++]); }
Either (a) it will be individually allocated. In this case marvel_MT
should be defined with a new MT (memory type) number, and the macro
ALLOCATE_INDIVIDUALLY(marvel)
should be expanded. The first and last
objects created will be FIRST_OBJECT(marvel)
and LAST_OBJECT(marvel)
,
and we can proceed either way through a double linked list of them with
PREV_OBJECT(mv, marvel)
and NEXT_OBJECT(mv, marvel)
. For convenience,
we can loop through marvels, in creation order, using LOOP_OVER(var,
marvel), which expands to a
for loop in which the variable
var runs
through each created marvel in turn; or equally we can run backwards
through using LOOP_BACKWARDS_OVER(var, marvel)
. In addition, there are
corruption checks to protect the memory from overrunning accidents, and the
structure can be used as a value in the symbols table. Good for large
structures with significant semantic content.
Or (b) it will be allocated in arrays. Once again we can obtain new marvels
with CREATE(marvel)
. This is more efficient both in speed and memory
usage, but we lose the ability to loop through the objects. For this
arrangement, define marvel_array_MT
with a new MT number and expand the
macro ALLOCATE_IN_ARRAYS(marvel, 100)
, where 100 (or what may you) is the
number of objects allocated jointly as a block. Good for small structures
used in the lower levels.
Here goes, then.
ALLOCATE_INDIVIDUALLY(auxiliary_file) ALLOCATE_INDIVIDUALLY(skein_node) ALLOCATE_INDIVIDUALLY(chunk_metadata) ALLOCATE_INDIVIDUALLY(placeholder) ALLOCATE_INDIVIDUALLY(heading) ALLOCATE_INDIVIDUALLY(table) ALLOCATE_INDIVIDUALLY(rdes_record) ALLOCATE_INDIVIDUALLY(segment) ALLOCATE_INDIVIDUALLY(request) ALLOCATE_INDIVIDUALLY(template) ALLOCATE_INDIVIDUALLY(template_path)
char cblorb_tolower(char c) { return (char) tolower((int) c); } char cblorb_toupper(char c) { return (char) toupper((int) c); } int cblorb_strlen(const char *p) { return (int) strlen(p); }
The function cblorb_tolower is used in 1/text (§9), 3/sol (§10), 3/links (§2).
The function cblorb_toupper is used in 3/sol (§9).
The function cblorb_strlen is used in 1/main (§7.2), 1/text (§5, §7.1, §8, §11), 1/blurb (§7.1, §7.2.1, §7.2.2), 2/blorb (§13, §14, §21, §22), 3/rel (§6.4, §8.1), 3/sol (§6, §7, §10.1), 3/links (§2), 3/place (§6).
Purpose: To read text files of whatever flavour, one line at a time.
Definitions:
typedef struct text_file_position { char text_file_filename[MAX_FILENAME_LENGTH]; int line_count; int line_position; int skip_terminator; int actively_scanning; whether we are still interested in the rest of the file } text_file_position;
The structure text_file_position is private to this section.
§2. Text file positions. This is useful for error messages:
void describe_file_position(char *t, text_file_position *tfp) { *t = 0; if (tfp == NULL) return; sprintf(t, "%s, line %d: ", tfp->text_file_filename, tfp->line_count); }
The function describe_file_position is used in §5.
int tfp_get_line_count(text_file_position *tfp) { if (tfp == NULL) return 0; return tfp->line_count; }
The function tfp_get_line_count is used in 1/blurb (§7), 3/web (§14, §22).
void tfp_lose_interest(text_file_position *tfp) { tfp->actively_scanning = FALSE; }
The function tfp_lose_interest is used in 3/web (§21).
text_file_position *error_position = NULL; void set_error_position(text_file_position *tfp) { error_position = tfp; } void error(char *erm) { char err[MAX_FILENAME_LENGTH]; describe_file_position(err, error_position); sprintf(err+cblorb_strlen(err), "Error: %s\n", erm); spool_error(err); } void error_1(char *erm, char *s) { char err[MAX_FILENAME_LENGTH]; describe_file_position(err, error_position); sprintf(err+cblorb_strlen(err), "Error: %s: '%s'\n", erm, s); spool_error(err); } void errorf_1s(char *erm, char *s1) { char err[MAX_FILENAME_LENGTH]; sprintf(err, erm, s1); spool_error(err); } void errorf_2s(char *erm, char *s1, char *s2) { char err[MAX_FILENAME_LENGTH]; sprintf(err, erm, s1, s2); spool_error(err); } void fatal(char *erm) { char err[MAX_FILENAME_LENGTH]; describe_file_position(err, error_position); sprintf(err+cblorb_strlen(err), "Fatal error: %s\n", erm); spool_error(err); print_report(); exit(1); } void fatal_fs(char *erm, char *fn) { char err[MAX_FILENAME_LENGTH]; describe_file_position(err, error_position); sprintf(err+cblorb_strlen(err), "Fatal error: %s: filename '%s'\n", erm, fn); spool_error(err); print_report(); exit(1); } void warning_fs(char *erm, char *fn) { char err[MAX_FILENAME_LENGTH]; describe_file_position(err, error_position); fprintf(stderr, "%sWarning: %s: filename '%s'\n", err, erm, fn); }
The function set_error_position is used in 1/blurb (§1, §7).
The function error is used in 1/main (¶6, §7.2, §7.2.1, §11), 1/blurb (§7.1), 3/sol (§3, §5, §6, §7), 3/links (§2), 3/place (§6).
The function error_1 is used in §7.1, §7.2, §7.3.2, 1/blurb (§7.1, §7.2), 3/rel (§6.7, §8.1, §9), 3/web (§9).
The function errorf_1s is used in 3/rel (§6.4), 3/templ (§6).
The function errorf_2s is used in 3/rel (§8.3.1).
The function fatal is used in §11, 1/mem (§10.1, §14, §15.1), 1/blurb (§7, §7.1, §7.2), 2/blorb (§8, §11, §17, §19, §21), 3/web (§9).
The function fatal_fs is used in §7.1, §7.2, §7.3, §7.3.2, §11, 2/blorb (§24, §24.3.1), 3/sol (§13), 3/b64 (§2).
The function warning_fs is used in §7.3.
§6. Errors are spooled to a placeholder, for the benefit of the report:
void spool_error(char *err) { append_to_placeholder("CBLORBERRORS", "<li>"); append_to_placeholder("CBLORBERRORS", err); append_to_placeholder("CBLORBERRORS", "</li>"); fprintf(stderr, "%s", err); error_count++; }
The function spool_error is used in §5.
void file_read(char *filename, char *message, int serious, void (iterator)(char *, text_file_position *), text_file_position *start_at) { FILE *HANDLE; text_file_position tfp; <Open the text file 7.1>; <Set the initial position, seeking it in the file if need be 7.2>; <Read in lines and send them one by one to the iterator 7.3>; fclose(HANDLE); }
The function file_read is used in 1/blurb (§1), 3/rel (§6.8, §6.9.1), 3/sol (§4), 3/web (§9, §13, §20.3).
§7.1.
<Open the text file 7.1> =
if (cblorb_strlen(filename) >= MAX_FILENAME_LENGTH) { if (serious) fatal_fs("filename too long", filename); error_1("filename too long", filename); return; } HANDLE = fopen(filename, "rb"); if (HANDLE == NULL) { if (message == NULL) return; if (serious) fatal_fs(message, filename); else { error_1(message, filename); return; } }
This code is used in §7.
<Set the initial position, seeking it in the file if need be 7.2> =
if (start_at == NULL) { tfp.line_count = 1; tfp.line_position = 0; tfp.skip_terminator = 'X'; } else { tfp = *start_at; if (fseek(HANDLE, (long int) (tfp.line_position), SEEK_SET)) { if (serious) fatal_fs("unable to seek position in file", filename); error_1("unable to seek position in file", filename); return; } } tfp.actively_scanning = TRUE; strcpy(tfp.text_file_filename, filename);
This code is used in §7.
<Read in lines and send them one by one to the iterator 7.3> =
char line[MAX_TEXT_FILE_LINE_LENGTH+1]; int i = 0, c = ' '; int warned = FALSE; while ((c != EOF) && (tfp.actively_scanning)) { c = fgetc(HANDLE); if ((c == EOF) || (c == '\x0a') || (c == '\x0d')) { line[i] = 0; if ((i > 0) || (c != tfp.skip_terminator)) { <Feed the completed line to the iterator routine 7.3.1>; if (c == '\x0a') tfp.skip_terminator = '\x0d'; if (c == '\x0d') tfp.skip_terminator = '\x0a'; } else tfp.skip_terminator = 'X'; <Update the text file position 7.3.2>; i = 0; } else { if (i < MAX_TEXT_FILE_LINE_LENGTH) line[i++] = (char) c; else { if (serious) fatal_fs("line too long", filename); if (warned == FALSE) { warning_fs("line too long (truncating it)", filename); warned = TRUE; } } } } if ((i > 0) && (tfp.actively_scanning)) <Feed the completed line to the iterator routine 7.3.1>;
This code is used in §7.
§7.3.1. We update the line counter only when a line is actually sent:
<Feed the completed line to the iterator routine 7.3.1> =
iterator(line, &tfp); tfp.line_count++;
This code is used in §7.3 (twice).
Properly speaking, ftell
returns a long int
, not an int
, but on a
32-bit integer machine — which Inform requires — this gives us room for files
to run to 2GB. Text files seldom come that large.
<Update the text file position 7.3.2> =
tfp.line_position = (int) (ftell(HANDLE)); if (tfp.line_position == -1) { if (serious) fatal_fs("unable to determine position in file", filename); error_1("unable to determine position in file", filename); }
This code is used in §7.3.
char *trim_white_space(char *original) { int i; for (i=0; white_space(original[i]); i++) ; original += i; for (i=cblorb_strlen(original)-1; ((i>=0) && (white_space(original[i]))); i--) original[i] = 0; return original; }
The function trim_white_space is used in 1/blurb (§7), 3/rel (§7, §8).
void extract_word(char *fword, char *line, int size, int word) { int i = 0; fword[0] = 0; while (word > 0) { word--; while (white_space(line[i])) i++; int j = 0; while ((line[i]) && (!white_space(line[i]))) { if (j < size-1) fword[j++] = cblorb_tolower(line[i]); i++; } fword[j] = 0; if (line[i] == 0) break; } if (word > 0) fword[0] = 0; }
The function extract_word is used in 3/web (§14.1).
§10. Where we define white space as spaces and tabs only:
int white_space(int c) { if ((c == ' ') || (c == '\t')) return TRUE; return FALSE; }
The function white_space is used in §8, §9.
char *get_filename_extension(char *filename) { int i = cblorb_strlen(filename) - 1; while ((i>=0) && (filename[i] != '.') && (filename[i] != SEP_CHAR)) i--; if ((i<0) || (filename[i] == SEP_CHAR)) return filename + cblorb_strlen(filename); return filename + i; } char *get_filename_leafname(char *filename) { int i = cblorb_strlen(filename) - 1; while ((i>=0) && (filename[i] != SEP_CHAR)) i--; return filename + i + 1; } int file_exists(char *filename) { FILE *TEST = fopen(filename, "r"); if (TEST) { fclose(TEST); return TRUE; } return FALSE; } long int file_size(char *filename) { FILE *TEST_FILE = fopen(filename, "rb"); if (TEST_FILE) { if (fseek(TEST_FILE, 0, SEEK_END) == 0) { long int file_size = ftell(TEST_FILE); if (file_size == -1L) fatal_fs("ftell failed on linked file", filename); fclose(TEST_FILE); return file_size; } else fatal_fs("fseek failed on linked file", filename); fclose(TEST_FILE); } return -1L; } int copy_file(char *from, char *to, int suppress_error) { if ((from == NULL) || (to == NULL) || (strcmp(from, to) == 0)) fatal("files confused in copier"); FILE *FROM = fopen(from, "rb"); if (FROM == NULL) { if (suppress_error == FALSE) fatal_fs("unable to read file", from); return -1; } FILE *TO = fopen(to, "wb"); if (TO == NULL) { fatal_fs("unable to write to file", to); return -1; } int size = 0; while (TRUE) { int c = fgetc(FROM); if (c == EOF) break; size++; putc(c, TO); } fclose(FROM); fclose(TO); return size; }
The function get_filename_extension is used in 2/blorb (§17, §19, §22), 3/rel (§9), 3/links (§2).
The function get_filename_leafname is used in 1/blurb (§7.2, §7.2.1), 3/links (§2), 3/web (§15).
The function file_exists is used in 3/templ (§4, §7).
The function file_size is used in 2/blorb (§8.2), 3/links (§5).
The function copy_file is used in 3/rel (§6.2, §6.3, §6.4, §6.9, §9.2).
Purpose: To read and follow the instructions in the blurb file, our main input.
§1. Reading the file. We divide the file into blurb commands at line breaks, so:
void parse_blurb_file(char *in) { file_read(in, "can't open blurb file", TRUE, interpret, 0); set_error_position(NULL); }
The function parse_blurb_file is used in 1/main (§7).
define author_COMMAND 0 define auxiliary_COMMAND 1 define base64_COMMAND 2 define copyright_COMMAND 3 define cover_COMMAND 4 define css_COMMAND 5 define ifiction_COMMAND 6 define ifiction_public_COMMAND 7 define ifiction_file_COMMAND 8 define interpreter_COMMAND 9 define palette_COMMAND 10 define palette_16_bit_COMMAND 11 define palette_32_bit_COMMAND 12 define picture_scaled_COMMAND 13 define picture_COMMAND 14 define picture_text_COMMAND 15 define picture_noid_COMMAND 16 define picture_with_alt_text_COMMAND 17 define placeholder_COMMAND 18 define project_folder_COMMAND 19 define release_COMMAND 20 define release_file_COMMAND 21 define release_file_from_COMMAND 22 define release_source_COMMAND 23 define release_to_COMMAND 24 define resolution_max_COMMAND 25 define resolution_min_max_COMMAND 26 define resolution_min_COMMAND 27 define resolution_COMMAND 28 define solution_COMMAND 29 define solution_public_COMMAND 30 define sound_music_COMMAND 31 define sound_repeat_COMMAND 32 define sound_forever_COMMAND 33 define sound_song_COMMAND 34 define sound_COMMAND 35 define sound_text_COMMAND 36 define sound_noid_COMMAND 37 define sound_with_alt_text_COMMAND 38 define source_COMMAND 39 define source_public_COMMAND 40 define status_COMMAND 41 define status_alternative_COMMAND 42 define status_instruction_COMMAND 43 define storyfile_include_COMMAND 44 define storyfile_COMMAND 45 define storyfile_leafname_COMMAND 46 define template_path_COMMAND 47 define website_COMMAND 48
§3. A single number specifying various possible combinations of operands:
define OPS_NO 1 define OPS_1TEXT 2 define OPS_2TEXT 3 define OPS_2TEXT_1NUMBER 4 define OPS_1NUMBER 5 define OPS_2NUMBER 6 define OPS_1NUMBER_1TEXT 7 define OPS_1NUMBER_2TEXTS 8 define OPS_1NUMBER_1TEXT_1NUMBER 9 define OPS_3NUMBER 10 define OPS_3TEXT 11
typedef struct blurb_command { char *explicated; plain English form of the command char *prototype;sscanf
prototype int operands; one of the aboveOPS_*
codes int deprecated; } blurb_command;
The structure blurb_command is private to this section.
In blurb syntax, a line whose first non-white-space character is an
exclamation mark !
is a comment, and is ignored. (This is the I6
comment character, too.) It appears in the table as a command
but, as we shall see, has no effect.
blurb_command syntaxes[] = { { "author \"name\"", "author \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "auxiliary \"filename\" \"description\" \"subfolder\"", "auxiliary \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" %n", OPS_3TEXT, FALSE }, { "base64 \"filename\" to \"filename\"", "base64 \"%[^\"]\" to \"%[^\"]\" %n", OPS_2TEXT, FALSE }, { "copyright \"message\"", "copyright \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "cover \"filename\"", "cover \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "css", "css %n", OPS_NO, FALSE }, { "ifiction", "ifiction %n", OPS_NO, FALSE }, { "ifiction public", "ifiction public %n", OPS_NO, FALSE }, { "ifiction \"filename\" include", "ifiction \"%[^\"]\" include %n", OPS_1TEXT, FALSE }, { "interpreter \"interpreter-name\" \"vm-letter\"", "interpreter \"%[^\"]\" \"%[gz]\" %n", OPS_2TEXT, FALSE }, { "palette { details }", "palette {%[^}]} %n", OPS_1TEXT, TRUE }, { "palette 16 bit", "palette 16 bit %n", OPS_NO, TRUE }, { "palette 32 bit", "palette 32 bit %n", OPS_NO, TRUE }, { "picture ID \"filename\" scale ...", "picture %20[A-Za-z0-9_] \"%[^\"]\" scale %s %n", OPS_3TEXT, TRUE }, { "picture N \"filename\"", "picture %d \"%[^\"]\" %n", OPS_1NUMBER_1TEXT, FALSE }, { "picture ID \"filename\"", "picture %20[A-Za-z0-9_] \"%[^\"]\" %n", OPS_2TEXT, FALSE }, { "picture \"filename\"", "picture \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "picture N \"filename\" \"alt-text\"", "picture %d \"%[^\"]\" \"%[^\"]\" %n", OPS_1NUMBER_2TEXTS, FALSE }, { "placeholder [name] = \"text\"", "placeholder [%[A-Z]] = \"%[^\"]\" %n", OPS_2TEXT, FALSE }, { "project folder \"pathname\"", "project folder \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "release \"text\"", "release \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "release file \"filename\"", "release file \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "release file \"filename\" from \"template\"", "release file \"%[^\"]\" from \"%[^\"]\" %n", OPS_2TEXT, FALSE }, { "release source \"filename\" using \"filename\" from \"template\"", "release source \"%[^\"]\" using \"%[^\"]\" from \"%[^\"]\" %n", OPS_3TEXT, FALSE }, { "release to \"pathname\"", "release to \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "resolution NxN max NxN", "resolution %d max %d %n", OPS_2NUMBER, TRUE }, { "resolution NxN min NxN max NxN", "resolution %d min %d max %d %n", OPS_3NUMBER, TRUE }, { "resolution NxN min NxN", "resolution %d min %d %n", OPS_2NUMBER, TRUE }, { "resolution NxN", "resolution %d %n", OPS_1NUMBER, TRUE }, { "solution", "solution %n", OPS_NO, FALSE }, { "solution public", "solution public %n", OPS_NO, FALSE }, { "sound ID \"filename\" music", "sound %20[A-Za-z0-9_] \"%[^\"]\" music %n", OPS_2TEXT, TRUE }, { "sound ID \"filename\" repeat N", "sound %20[A-Za-z0-9_] \"%[^\"]\" repeat %d %n", OPS_2TEXT_1NUMBER, TRUE }, { "sound ID \"filename\" repeat forever", "sound %20[A-Za-z0-9_] \"%[^\"]\" repeat forever %n", OPS_2TEXT, TRUE }, { "sound ID \"filename\" song", "sound %20[A-Za-z0-9_] \"%[^\"]\" song %n", OPS_2TEXT, TRUE }, { "sound N \"filename\"", "sound %d \"%[^\"]\" %n", OPS_1NUMBER_1TEXT, FALSE }, { "sound ID \"filename\"", "sound %20[A-Za-z0-9_] \"%[^\"]\" %n", OPS_2TEXT, FALSE }, { "sound \"filename\"", "sound \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "sound N \"filename\" \"alt-text\"", "sound %d \"%[^\"]\" \"%[^\"]\" %n", OPS_1NUMBER_2TEXTS, FALSE }, { "source", "source %n", OPS_NO, FALSE }, { "source public", "source public %n", OPS_NO, FALSE }, { "status \"template\" \"filename\"", "status \"%[^\"]\" \"%[^\"]\" %n", OPS_2TEXT, FALSE }, { "status alternative ||link to Inform documentation||", "status alternative ||%[^|]|| %n", OPS_1TEXT, FALSE }, { "status instruction ||link to Inform source text||", "status instruction ||%[^|]|| %n", OPS_1TEXT, FALSE }, { "storyfile \"filename\" include", "storyfile \"%[^\"]\" include %n", OPS_1TEXT, FALSE }, { "storyfile \"filename\"", "storyfile \"%[^\"]\" %n", OPS_1TEXT, TRUE }, { "storyfile leafname \"leafname\"", "storyfile leafname \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "template path \"folder\"", "template path \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { "website \"template\"", "website \"%[^\"]\" %n", OPS_1TEXT, FALSE }, { NULL, NULL, OPS_NO, FALSE } };
§6. Summary. For the -help
information:
void summarise_blurb(void) { int t; printf("\nThe blurbfile is a script of commands, one per line, in these forms:\n"); for (t=0; syntaxes[t].prototype; t++) if (syntaxes[t].deprecated == FALSE) printf(" %s\n", syntaxes[t].explicated); printf("\nThe following syntaxes, though legal in Blorb 2001, are not supported:\n"); for (t=0; syntaxes[t].prototype; t++) if (syntaxes[t].deprecated == TRUE) printf(" %s\n", syntaxes[t].explicated); }
The function summarise_blurb is used in 1/main (§7.3).
void interpret(char *command, text_file_position *tf) { set_error_position(tf); if (command == NULL) fatal("null blurb line"); command = trim_white_space(command); if (command[0] == 0) return; thus skip a line containing only blank space if (command[0] == '!') return; thus skip a comment line if (trace_mode) fprintf(stdout, "! %03d: %s\n", tfp_get_line_count(tf), command); int outcome = -1; which of the legal command syntaxes is used char text1[MAX_TEXT_FILE_LINE_LENGTH], text2[MAX_TEXT_FILE_LINE_LENGTH], text3[MAX_TEXT_FILE_LINE_LENGTH]; text1[0] = 0; text2[0] = 0; text3[0] = 0; int num1 = 0, num2 = 0, num3 = 0; <Parse the command and set operands appropriately 7.1>; <Take action on the command 7.2>; }
The function interpret is used in §1.
<Parse the command and set operands appropriately 7.1> =
int t; for (t=0; syntaxes[t].prototype; t++) { char *pr = syntaxes[t].prototype; int nm = -1; number of characters matched switch (syntaxes[t].operands) { case OPS_NO: sscanf(command, pr, &nm); break; case OPS_1TEXT: sscanf(command, pr, text1, &nm); break; case OPS_2TEXT: sscanf(command, pr, text1, text2, &nm); break; case OPS_2TEXT_1NUMBER: sscanf(command, pr, text1, text2, &num1, &nm); break; case OPS_1NUMBER: sscanf(command, pr, &num1, &nm); break; case OPS_2NUMBER: sscanf(command, pr, &num1, &num2, &nm); break; case OPS_1NUMBER_1TEXT: sscanf(command, pr, &num1, text1, &nm); break; case OPS_1NUMBER_2TEXTS: sscanf(command, pr, &num1, text1, text2, &nm); break; case OPS_1NUMBER_1TEXT_1NUMBER: sscanf(command, pr, &num1, text1, &num2, &nm); break; case OPS_3NUMBER: sscanf(command, pr, &num1, &num2, &num3, &nm); break; case OPS_3TEXT: sscanf(command, pr, text1, text2, text3, &nm); break; default: fatal("unknown operand type"); } if (nm == cblorb_strlen(command)) { outcome = t; break; } } if ((cblorb_strlen(text1) >= MAX_FILENAME_LENGTH-1) || (cblorb_strlen(text2) >= MAX_FILENAME_LENGTH-1) || (cblorb_strlen(text3) >= MAX_FILENAME_LENGTH-1)) { error("string too long"); return; } if (outcome == -1) { error_1("not a valid blurb command", command); return; } if (syntaxes[outcome].deprecated) { error_1("this Blurb syntax is no longer supported", syntaxes[outcome].explicated); return; }
This code is used in §7.
§7.2. The command is now fully parsed, and is one that we support. We can act.
<Take action on the command 7.2> =
switch (outcome) { case author_COMMAND: set_placeholder_to("AUTHOR", text1, 0); author_chunk(text1); break; case auxiliary_COMMAND: create_auxiliary_file(text1, text2, text3); break; case base64_COMMAND: request_2(BASE64_REQ, text1, text2, FALSE); break; case copyright_COMMAND: copyright_chunk(text1); break; case cover_COMMAND: <Declare which file is the cover art 7.2.1>; break; case css_COMMAND: use_css_code_styles = TRUE; break; case ifiction_file_COMMAND: metadata_chunk(text1); break; case ifiction_COMMAND: request_1(IFICTION_REQ, "", TRUE); break; case ifiction_public_COMMAND: request_1(IFICTION_REQ, "", FALSE); break; case interpreter_COMMAND: set_placeholder_to("INTERPRETERVMIS", text2, 0); request_1(INTERPRETER_REQ, text1, FALSE); break; case picture_COMMAND: picture_chunk(num1, text1, ""); break; case picture_text_COMMAND: picture_chunk_text(text1, text2); break; case picture_noid_COMMAND: picture_chunk_text("", text1); break; case picture_with_alt_text_COMMAND: picture_chunk(num1, text1, text2); break; case placeholder_COMMAND: set_placeholder_to(text1, text2, 0); break; case project_folder_COMMAND: strcpy(project_folder, text1); break; case release_COMMAND: set_placeholder_to_number("RELEASE", num1); release_chunk(num1); break; case release_file_COMMAND: request_3(COPY_REQ, text1, get_filename_leafname(text1), "--", FALSE); break; case release_file_from_COMMAND: request_2(RELEASE_FILE_REQ, text1, text2, FALSE); break; case release_to_COMMAND: strcpy(release_folder, text1); <Make pathname placeholders in three different formats 7.2.2>; break; case release_source_COMMAND: request_3(RELEASE_SOURCE_REQ, text1, text2, text3, FALSE); break; case solution_COMMAND: request_1(SOLUTION_REQ, "", TRUE); break; case solution_public_COMMAND: request_1(SOLUTION_REQ, "", FALSE); break; case sound_COMMAND: sound_chunk(num1, text1, ""); break; case sound_text_COMMAND: sound_chunk_text(text1, text2); break; case sound_noid_COMMAND: sound_chunk_text("", text1); break; case sound_with_alt_text_COMMAND: sound_chunk(num1, text1, text2); break; case source_COMMAND: request_1(SOURCE_REQ, "", TRUE); break; case source_public_COMMAND: request_1(SOURCE_REQ, "", FALSE); break; case status_COMMAND: strcpy(status_template, text1); strcpy(status_file, text2); break; case status_alternative_COMMAND: request_1(ALTERNATIVE_REQ, text1, FALSE); break; case status_instruction_COMMAND: request_1(INSTRUCTION_REQ, text1, FALSE); break; case storyfile_include_COMMAND: executable_chunk(text1); break; case storyfile_leafname_COMMAND: set_placeholder_to("STORYFILE", text1, 0); break; case template_path_COMMAND: new_template_path(text1); break; case website_COMMAND: request_1(WEBSITE_REQ, text1, FALSE); break; default: error_1("***", command); fatal("*** command unimplemented ***\n"); }
This code is used in §7.
<Declare which file is the cover art 7.2.1> =
set_placeholder_to("BIGCOVER", text1, 0); cover_exists = TRUE; cover_is_in_JPEG_format = TRUE; if ((text1[cblorb_strlen(text1)-3] == 'p') || (text1[cblorb_strlen(text1)-3] == 'P')) cover_is_in_JPEG_format = FALSE; frontispiece_chunk(1); char *leaf = get_filename_leafname(text1); if (strcmp(leaf, "DefaultCover.jpg") == 0) default_cover_used = TRUE; if (cover_is_in_JPEG_format) strcpy(leaf, "Small Cover.jpg"); else strcpy(leaf, "Small Cover.png"); set_placeholder_to("SMALLCOVER", text1, 0);
This code is used in §7.2.
However, we also need two variants on the pathname, one to be supplied to the
Javascript function openUrl
and one to fileUrl
. For platform dependency
reasons these need to be manipulated to deal with awkward characters.
<Make pathname placeholders in three different formats 7.2.2> =
set_placeholder_to("MATERIALSFOLDERPATH", text1, 0); int k = cblorb_strlen(text1); while ((k>=0) && (text1[k] != SEP_CHAR)) k--; if (k>0) { *(read_placeholder("MATERIALSFOLDERPATH")+k)=0; k--; } while ((k>=0) && (text1[k] != SEP_CHAR)) k--; k++; set_placeholder_to("MATERIALSFOLDER", text1 + k, 0); char *L = read_placeholder("MATERIALSFOLDER"); while (*L) { if (*L == SEP_CHAR) *L = 0; L++; } qualify_placeholder("MATERIALSFOLDERPATHOPEN", "MATERIALSFOLDERPATHFILE", "MATERIALSFOLDERPATH");
This code is used in §7.2.
void qualify_placeholder(char *openUrl_path, char *fileUrl_path, char *original) { int i; char *p = read_placeholder(original); for (i=0; p[i]; i++) { char oU_glyph[8], fU_glyph[8]; sprintf(oU_glyph, "%c", p[i]); sprintf(fU_glyph, "%c", p[i]); if (p[i] == ' ') { if (escape_openUrl) sprintf(oU_glyph, "%%2520"); if (escape_fileUrl) sprintf(fU_glyph, "%%2520"); } if (p[i] == '\\') { if (reverse_slash_openUrl) sprintf(oU_glyph, "/"); if (reverse_slash_fileUrl) sprintf(fU_glyph, "/"); } append_to_placeholder(openUrl_path, oU_glyph); append_to_placeholder(fileUrl_path, fU_glyph); } }
The function qualify_placeholder is used in §7.2.2.
Purpose: To write the Blorb file, our main output, to disc.
Definitions:
int total_size_of_Blorb_chunks = 0; ditto, but not counting theFORM
header or theRIdx
chunk int no_indexed_chunks = 0;
Our IFF file will consist of a front part and then the chunks, one after
another, in order of their creation. Every chunk has a type, a 4-character ID
like "AUTH"
or "JPEG"
, specifying what kind of data it holds; some
chunks are also given resource", " numbers which allow the story file to refer
to them as it runs — the pictures, sound effects and the story file itself
all have unique resource numbers. (These are called "indexed", because
references to them appear in a special RIdx
record in the front part
of the file — the "resource index".)
typedef struct chunk_metadata { char filename[MAX_FILENAME_LENGTH]; if the content is stored on disc unsigned char data_in_memory[MAX_FILENAME_LENGTH]; if the content is stored in memory int length_of_data_in_memory; in bytes; or $-1$ if the content is stored on disc char *chunk_type; pointer to a 4-character string char *index_entry; ditto int resource_id; meaningful only if this is a chunk which is indexed int byte_offset; from the start of the chunks, which is not quite the start of the IFF file int size; in bytes MEMORY_MANAGEMENT } chunk_metadata;
The structure chunk_metadata is private to this section.
typedef struct resource_list resource_list; struct resource_list { int num; struct resource_list *n; }; resource_list *sound_resource = NULL; resource_list *pict_resource = NULL;
The structure resource_list is private to this section.
typedef struct rdes_record { int usage; int resource_id; char *description; MEMORY_MANAGEMENT } rdes_record;
The structure rdes_record is accessed in 3/links and here.
void four_word(FILE *F, int n) { fputc((n / 0x1000000)%0x100, F); fputc((n / 0x10000)%0x100, F); fputc((n / 0x100)%0x100, F); fputc((n)%0x100, F); } void two_word(FILE *F, int n) { fputc((n / 0x100)%0x100, F); fputc((n)%0x100, F); } void one_byte(FILE *F, int n) { fputc((n)%0x100, F); } void s_four_word(unsigned char *F, int n) { F[0] = (unsigned char) (n / 0x1000000)%0x100; F[1] = (unsigned char) (n / 0x10000)%0x100; F[2] = (unsigned char) (n / 0x100)%0x100; F[3] = (unsigned char) (n)%0x100; } void s_two_word(unsigned char *F, int n) { F[0] = (unsigned char) (n / 0x100)%0x100; F[1] = (unsigned char) (n)%0x100; } void s_one_byte(unsigned char *F, int n) { F[0] = (unsigned char) (n)%0x100; }
The function four_word is used in §24.2, §24.3.
The function two_word appears nowhere else.
The function one_byte is used in §24.3, §24.3.1, §24.3.2.
The function s_four_word is used in §15, §21.
The function s_two_word is used in §16.
The function s_one_byte appears nowhere else.
chunk_metadata *current_chunk = NULL;
void add_chunk_to_blorb(char *id, int resource_num, char *supplied_filename, char *index, unsigned char *data, int length) { if (chunk_type_is_legal(id) == FALSE) fatal("tried to complete non-Blorb chunk"); if (index_entry_is_legal(index) == FALSE) fatal("tried to include mis-indexed chunk"); current_chunk = CREATE(chunk_metadata); <Set the filename for the new chunk 8.1>; current_chunk->chunk_type = id; current_chunk->index_entry = index; if (current_chunk->index_entry) no_indexed_chunks++; current_chunk->byte_offset = total_size_of_Blorb_chunks; current_chunk->resource_id = resource_num; <Compute the size in bytes of the chunk 8.2>; <Advance the total chunk size 8.3>; if (trace_mode) printf("! Begun chunk %s: fn is <%s> (innate size %d)\n", current_chunk->chunk_type, current_chunk->filename, current_chunk->size); }
The function add_chunk_to_blorb is used in §13, §14, §15, §16, §17, §19, §21, §22, §23.
§8.1.
<Set the filename for the new chunk 8.1> =
if (data) { strcpy(current_chunk->filename, "(not from a file)"); current_chunk->length_of_data_in_memory = length; int i; for (i=0; i<length; i++) current_chunk->data_in_memory[i] = data[i]; } else { strcpy(current_chunk->filename, supplied_filename); current_chunk->length_of_data_in_memory = -1; }
This code is used in §8.
§8.2.
<Compute the size in bytes of the chunk 8.2> =
int size; if (data) { size = length; } else { size = (int) file_size(supplied_filename); } if (chunk_type_is_already_an_IFF(current_chunk->chunk_type) == FALSE) size += 8; allow 8 further bytes for the chunk header to be added later current_chunk->size = size;
This code is used in §8.
<Advance the total chunk size 8.3> =
total_size_of_Blorb_chunks += current_chunk->size; if ((current_chunk->size) % 2 == 1) total_size_of_Blorb_chunks++;
This code is used in §8.
The weasel words "with the above..." are because we will also generate two
chunks separately: the compulsory "FORM"
chunk enclosing the entire Blorb, and
an indexing chunk, "RIdx"
. Within this index, some chunks appear, but not
others, and they are labelled with the "index entry" text.
char *legal_Blorb_chunk_types[] = { "AUTH", "(c) ", "Fspc", "RelN", "IFmd", miscellaneous identifying data "JPEG", "PNG ", images in different formats "AIFF", "OGGV", "MIDI", "MOD ", sound effects in different formats "ZCOD", "GLUL", story files in different formats "RDes", resource descriptions (added to the standard in March 2014) NULL }; char *legal_Blorb_index_entries[] = { "Pict", "Snd ", "Exec", NULL };
§10. Because we are wisely paranoid:
int chunk_type_is_legal(char *type) { int i; if (type == NULL) return FALSE; for (i=0; legal_Blorb_chunk_types[i]; i++) if (strcmp(type, legal_Blorb_chunk_types[i]) == 0) return TRUE; return FALSE; } int index_entry_is_legal(char *entry) { int i; if (entry == NULL) return TRUE; for (i=0; legal_Blorb_index_entries[i]; i++) if (strcmp(entry, legal_Blorb_index_entries[i]) == 0) return TRUE; return FALSE; }
The function chunk_type_is_legal is used in §8.
The function index_entry_is_legal is used in §8.
int resource_seen(resource_list **list, int value) { resource_list *p; for(p = *list; p; p = p->n) { if (p->num == value) return TRUE; } p = *list; *list = malloc(sizeof(resource_list)); if (*list == NULL) fatal("Run out of memory: malloc failed"); (*list)->num = value; (*list)->n = p; return FALSE; }
The function resource_seen is used in §17, §19.
int chunk_type_is_already_an_IFF(char *type) { if (strcmp(type, "AIFF")==0) return TRUE; return FALSE; }
The function chunk_type_is_already_an_IFF is used in §8.2, §24.3.
§13. "AUTH"
: author's name, as a null-terminated string.
void author_chunk(char *t) { if (trace_mode) printf("! Author: <%s>\n", t); add_chunk_to_blorb("AUTH", 0, NULL, NULL, (unsigned char *) t, cblorb_strlen(t)); }
The function author_chunk is used in 1/blurb (§7.2).
§14. "(c) "
: copyright declaration.
void copyright_chunk(char *t) { if (trace_mode) printf("! Copyright declaration: <%s>\n", t); add_chunk_to_blorb("(c) ", 0, NULL, NULL, (unsigned char *) t, cblorb_strlen(t)); }
The function copyright_chunk is used in 1/blurb (§7.2).
void frontispiece_chunk(int pn) { if (trace_mode) printf("! Frontispiece is image %d\n", pn); unsigned char data[4]; s_four_word(data, pn); add_chunk_to_blorb("Fspc", 0, NULL, NULL, data, 4); }
The function frontispiece_chunk is used in 1/blurb (§7.2.1).
void release_chunk(int rn) { if (trace_mode) printf("! Release number is %d\n", rn); unsigned char data[2]; s_two_word(data, rn); add_chunk_to_blorb("RelN", 0, NULL, NULL, data, 2); }
The function release_chunk is used in 1/blurb (§7.2).
void picture_chunk(int n, char *fn, char *alt) { char *p = get_filename_extension(fn); char *type = "PNG "; if (n < 1) fatal("Picture resource number is less than 1"); if (resource_seen(&pict_resource, n)) fatal("Duplicate Picture resource number"); if (*p == '.') { p++; if ((*p == 'j') || (*p == 'J')) type = "JPEG"; } add_chunk_to_blorb(type, n, fn, "Pict", NULL, 0); if ((alt) && (alt[0])) add_rdes_record(1, n, alt); no_pictures_included++; }
The function picture_chunk is used in §18, 1/blurb (§7.2).
void picture_chunk_text(char *name, char *fn) { if (name[0] == 0) { printf("! Null picture ID, using %d\n", picture_resource_num); } else { printf("Constant PICTURE_%s = %d;\n", name, picture_resource_num); } picture_resource_num++; picture_chunk(picture_resource_num, fn, ""); }
The function picture_chunk_text is used in 1/blurb (§7.2).
There can be any number of these chunks, too.
void sound_chunk(int n, char *fn, char *alt) { char *p = get_filename_extension(fn); char *type = "AIFF"; if (n < 3) fatal("Sound resource number is less than 3"); if (resource_seen(&sound_resource, n)) fatal("Duplicate Sound resource number"); if (*p == '.') { p++; if ((*p == 'o') || (*p == 'O')) type = "OGGV"; else if ((*p == 'm') || (*p == 'M')) { if ((p[1] == 'i') || (p[1] == 'I')) type = "MIDI"; else type = "MOD "; } } add_chunk_to_blorb(type, n, fn, "Snd ", NULL, 0); if ((alt) && (alt[0])) add_rdes_record(2, n, alt); no_sounds_included++; }
The function sound_chunk is used in §20, 1/blurb (§7.2).
void sound_chunk_text(char *name, char *fn) { if (name[0] == 0) { printf("! Null sound ID, using %d\n", sound_resource_num); } else { printf("Constant SOUND_%s = %d;\n", name, sound_resource_num); } sound_resource_num++; sound_chunk(sound_resource_num, fn, ""); }
The function sound_chunk_text is used in 1/blurb (§7.2).
§21. "RDes"
: the resource description, a repository for alt-texts describing
images or sounds.
size_t size_of_rdes_chunk = 0; void add_rdes_record(int usage, int n, char *alt) { size_t S = strlen(alt); char *p = malloc(S+1); if (p == NULL) fatal("Run out of memory: malloc failed"); strcpy(p, alt); rdes_record *rr = CREATE(rdes_record); rr->usage = usage; rr->resource_id = n; rr->description = p; size_of_rdes_chunk += 12 + S; } void rdes_chunk(void) { if (size_of_rdes_chunk > 0) { unsigned char *rdes_data = (unsigned char *) malloc(size_of_rdes_chunk + 9); if (rdes_data == NULL) fatal("Run out of memory: malloc failed"); size_t pos = 4; s_four_word(rdes_data, NUMBER_CREATED(rdes_record)); rdes_record *rr; LOOP_OVER(rr, rdes_record) { if (rr->usage == 1) strcpy((char *) (rdes_data + pos), "Pict"); else if (rr->usage == 2) strcpy((char *) (rdes_data + pos), "Snd "); else s_four_word(rdes_data + pos, 0); s_four_word(rdes_data + pos + 4, rr->resource_id); s_four_word(rdes_data + pos + 8, cblorb_strlen(rr->description)); strcpy((char *) (rdes_data + pos + 12), rr->description); pos += 12 + strlen(rr->description); } if (pos != size_of_rdes_chunk + 4) fatal("misconstructed rdes"); add_chunk_to_blorb("RDes", 0, NULL, NULL, rdes_data, (int) pos); } }
The function add_rdes_record is used in §17, §19.
The function rdes_chunk is used in §24.
void executable_chunk(char *fn) { char *p = get_filename_extension(fn); char *type = "ZCOD"; if (*p == '.') { if (p[cblorb_strlen(p)-1] == 'x') type = "GLUL"; } add_chunk_to_blorb(type, 0, fn, "Exec", NULL, 0); }
The function executable_chunk is used in 1/blurb (§7.2).
void metadata_chunk(char *fn) { add_chunk_to_blorb("IFmd", 0, fn, NULL, NULL, 0); }
The function metadata_chunk is used in 1/blurb (§7.2).
void write_blorb_file(char *out) { rdes_chunk(); if (NUMBER_CREATED(chunk_metadata) == 0) return; FILE *IFF = fopen(out, "wb"); if (IFF == NULL) fatal_fs("can't open blorb file for output", out); int RIdx_size, first_byte_after_index; <Calculate the sizes of the whole file and the index chunk 24.1>; <Write the initial FORM chunk of the IFF file, and then the index 24.2>; if (trace_mode) <Print out a copy of the chunk table 24.4>; chunk_metadata *chunk; LOOP_OVER(chunk, chunk_metadata) <Write the chunk 24.3>; fclose(IFF); }
The function write_blorb_file is used in 1/main (§7).
That even extends to the file itself, which is a single IFF chunk of type
"FORM"
. So we need to think carefully. We will need the FORM
header,
then the header for the RIdx
indexing chunk, then the body of that indexing
chunk — with one record for each indexed chunk; and then room for all of
the chunks we'll copy in, whether they are indexed or not.
<Calculate the sizes of the whole file and the index chunk 24.1> =
int FORM_header_size = 12; int RIdx_header_size = 12; int index_entry_size = 12; RIdx_size = RIdx_header_size + index_entry_size*no_indexed_chunks; first_byte_after_index = FORM_header_size + RIdx_size; blorb_file_size = first_byte_after_index + total_size_of_Blorb_chunks;
This code is used in §24.
<Write the initial FORM chunk of the IFF file, and then the index 24.2> =
fprintf(IFF, "FORM"); four_word(IFF, blorb_file_size - 8); offset to end ofFORM
after the 8 bytes so far fprintf(IFF, "IFRS"); magic text identifying the IFF as a Blorb fprintf(IFF, "RIdx"); four_word(IFF, RIdx_size - 8); offset to end ofRIdx
after the 8 bytes so far four_word(IFF, no_indexed_chunks); i.e., number of entries in the index chunk_metadata *chunk; LOOP_OVER(chunk, chunk_metadata) if (chunk->index_entry) { fprintf(IFF, "%s", chunk->index_entry); four_word(IFF, chunk->resource_id); four_word(IFF, first_byte_after_index + chunk->byte_offset); }
This code is used in §24.
<Write the chunk 24.3> =
int bytes_to_copy; char *type = chunk->chunk_type; if (chunk_type_is_already_an_IFF(type) == FALSE) { fprintf(IFF, "%s", type); four_word(IFF, chunk->size - 8); offset to end of chunk after the 8 bytes so far bytes_to_copy = chunk->size - 8; since here the chunk size included 8 extra } else { bytes_to_copy = chunk->size; whereas here the chunk size was genuinely the file size } if (chunk->length_of_data_in_memory >= 0) <Copy that many bytes from memory 24.3.2> else <Copy that many bytes from the chunk file on the disc 24.3.1>; if ((bytes_to_copy % 2) == 1) one_byte(IFF, 0); as we allowed for above
This code is used in §24.
§24.3.1. Sometimes the chunk's contents are on disc:
<Copy that many bytes from the chunk file on the disc 24.3.1> =
FILE *CHUNKSUB = fopen(chunk->filename, "rb"); if (CHUNKSUB == NULL) fatal_fs("unable to read data", chunk->filename); else { int i; for (i=0; i<bytes_to_copy; i++) { int j = fgetc(CHUNKSUB); if (j == EOF) fatal_fs("chunk ran out incomplete", chunk->filename); one_byte(IFF, j); } fclose(CHUNKSUB); }
This code is used in §24.3.
§24.3.2. And sometimes, for shorter things, they are in memory:
<Copy that many bytes from memory 24.3.2> =
int i; for (i=0; i<bytes_to_copy; i++) { int j = (int) (chunk->data_in_memory[i]); one_byte(IFF, j); }
This code is used in §24.3.
§24.4. For debugging purposes only:
<Print out a copy of the chunk table 24.4> =
printf("! Chunk table:\n"); chunk_metadata *chunk; LOOP_OVER(chunk, chunk_metadata) printf("! Chunk %s %06x %s %d <%s>\n", chunk->chunk_type, chunk->size, (chunk->index_entry)?(chunk->index_entry):"unindexed", chunk->resource_id, chunk->filename); printf("! End of chunk table\n");
This code is used in §24.
Purpose: To manage requests to release material other than a Blorb file.
Definitions:
define COPY_REQ 0 a miscellaneous file define IFICTION_REQ 1 the iFiction record of a project define RELEASE_FILE_REQ 2 a template file define RELEASE_SOURCE_REQ 3 the source text in HTML form define SOLUTION_REQ 4 a solution file generated from the skein define SOURCE_REQ 5 the source text of a project define WEBSITE_REQ 6 a whole website define INTERPRETER_REQ 7 an in-browser interpreter define BASE64_REQ 8 a base64-encoded copy of a binary file define INSTRUCTION_REQ 9 a release instruction copied to cblorb for reporting only define ALTERNATIVE_REQ 10 an unused release instruction copied to cblorb for reporting only
int website_requested = FALSE; has a WEBSITE_REQ
been made?
¶2. This would use a lot of memory if there were many requests, but there are not and it does not.
typedef struct request {
int what_is_requested; one of the *_REQ
values above
char details1[MAX_FILENAME_LENGTH];
char details2[MAX_FILENAME_LENGTH];
char details3[MAX_FILENAME_LENGTH];
int private; is this request private, i.e., not to contribute to a website?
int outcome_data; e.g. number of bytes copied
MEMORY_MANAGEMENT
} request;
The structure request is private to this section.
§3. Receiving requests. These can have from 0 to 3 textual details attached:
request *request_0(int kind, int privacy) { request *req = CREATE(request); req->what_is_requested = kind; req->details1[0] = 0; req->details2[0] = 0; req->details3[0] = 0; req->private = privacy; req->outcome_data = 0; if (kind == WEBSITE_REQ) website_requested = TRUE; return req; } request *request_1(int kind, char *text1, int privacy) { request *req = request_0(kind, privacy); strcpy(req->details1, text1); return req; } request *request_2(int kind, char *text1, char *text2, int privacy) { request *req = request_0(kind, privacy); strcpy(req->details1, text1); strcpy(req->details2, text2); return req; } request *request_3(int kind, char *text1, char *text2, char *text3, int privacy) { request *req = request_0(kind, privacy); strcpy(req->details1, text1); strcpy(req->details2, text2); strcpy(req->details3, text3); return req; }
The function request_0 appears nowhere else.
The function request_1 is used in 1/blurb (§7.2).
The function request_2 is used in 1/blurb (§7.2).
The function request_3 is used in §4, 1/blurb (§7.2).
§4. A convenient abbreviation:
void request_copy(char *from, char *to, char *subfolder) { request_3(COPY_REQ, from, to, subfolder, FALSE); }
The function request_copy is used in §5, 3/links (§7).
void any_last_requests(void) { request_copy_of_auxiliaries(); if (default_cover_used == FALSE) { char *BIGCOVER = read_placeholder("BIGCOVER"); if (BIGCOVER) { if (cover_is_in_JPEG_format) request_copy(BIGCOVER, "Cover.jpg", "--"); else request_copy(BIGCOVER, "Cover.png", "--"); } if (website_requested) { char *SMALLCOVER = read_placeholder("SMALLCOVER"); if (SMALLCOVER) { if (cover_is_in_JPEG_format) request_copy(SMALLCOVER, "Small Cover.jpg", "--"); else request_copy(SMALLCOVER, "Small Cover.png", "--"); } } } }
The function any_last_requests is used in §6.
void create_requested_material(void) { if (release_folder[0] == 0) return; printf("! Release folder: <%s>\n", release_folder); if (blorb_file_size > 0) declare_where_blorb_should_be_copied(release_folder); any_last_requests(); request *req; LOOP_OVER(req, request) { switch (req->what_is_requested) { case ALTERNATIVE_REQ: break; case BASE64_REQ: <Copy a base64-encoded file across 6.5>; break; case COPY_REQ: <Copy a file into the release folder 6.4>; break; case IFICTION_REQ: <Create an iFiction file 6.3>; break; case INSTRUCTION_REQ: break; case INTERPRETER_REQ: <Create an in-browser interpreter 6.8>; break; case RELEASE_FILE_REQ: <Release a file into the release folder 6.6>; break; case RELEASE_SOURCE_REQ: <Release source text as HTML into the release folder 6.7>; break; case SOLUTION_REQ: <Create a walkthrough file 6.1>; break; case SOURCE_REQ: <Create a plain text source file 6.2>; break; case WEBSITE_REQ: <Create a website 6.9>; break; } } }
The function create_requested_material is used in 1/main (§7).
§6.1.
<Create a walkthrough file 6.1> =
char Skein_filename[MAX_FILENAME_LENGTH]; sprintf(Skein_filename, "%s%cSkein.skein", project_folder, SEP_CHAR); char solution_filename[MAX_FILENAME_LENGTH]; sprintf(solution_filename, "%s%csolution.txt", release_folder, SEP_CHAR); walkthrough(Skein_filename, solution_filename);
This code is used in §6.
§6.2.
<Create a plain text source file 6.2> =
char source_text_filename[MAX_FILENAME_LENGTH]; sprintf(source_text_filename, "%s%cSource%cstory.ni", project_folder, SEP_CHAR, SEP_CHAR); char write_to[MAX_FILENAME_LENGTH]; sprintf(write_to, "%s%csource.txt", release_folder, SEP_CHAR); copy_file(source_text_filename, write_to, FALSE);
This code is used in §6.
§6.3.
<Create an iFiction file 6.3> =
char iFiction_filename[MAX_FILENAME_LENGTH]; sprintf(iFiction_filename, "%s%cMetadata.iFiction", project_folder, SEP_CHAR); char write_to[MAX_FILENAME_LENGTH]; sprintf(write_to, "%s%ciFiction.xml", release_folder, SEP_CHAR); copy_file(iFiction_filename, write_to, FALSE);
This code is used in §6.
§6.4.
<Copy a file into the release folder 6.4> =
char write_to[MAX_FILENAME_LENGTH]; if (strcmp(req->details3, "--") == 0) { sprintf(write_to, "%s%c%s", release_folder, SEP_CHAR, req->details2); } else { sprintf(write_to, "%s%c%s%c%s", release_folder, SEP_CHAR, req->details3, SEP_CHAR, req->details2); } int size = copy_file(req->details1, write_to, TRUE); req->outcome_data = size; if (size == -1) { int i; for (i = cblorb_strlen(req->details1); i>0; i--) if ((req->details1)[i] == SEP_CHAR) { i++; break; } errorf_1s( "You asked to release along with a file called '%s', which ought " "to be in the Materials folder for the project. But I can't find " "it there.", (req->details1)+i); }
This code is used in §6.
§6.5.
<Copy a base64-encoded file across 6.5> =
encode_as_base64(req->details1, req->details2, read_placeholder("BASESIXTYFOURTOP"), read_placeholder("BASESIXTYFOURTAIL"));
This code is used in §6.
§6.6.
<Release a file into the release folder 6.6> =
release_file_into_website(req->details1, req->details2, NULL);
This code is used in §6.
§6.7.
<Release source text as HTML into the release folder 6.7> =
set_placeholder_to("SOURCEPREFIX", "source", 0); set_placeholder_to("SOURCELOCATION", req->details1, 0); set_placeholder_to("TEMPLATE", req->details3, 0); char *HTML_template = find_file_in_named_template(req->details3, req->details2); if (HTML_template == NULL) error_1("can't find HTML template file", req->details2); if (trace_mode) printf("! Web page %s from template %s\n", HTML_template, req->details3); web_copy_source(HTML_template, release_folder);
This code is used in §6.
<Create an in-browser interpreter 6.8> =
set_placeholder_to("INTERPRETER", req->details1, 0); char *t = read_placeholder("INTERPRETER"); char *from = find_file_in_named_template(t, "(manifest).txt"); if (from) { i.e., if the "(manifest).txt" file exists file_read(from, "can't open (manifest) file", FALSE, read_requested_ifile, 0); }
This code is used in §6.
<Create a website 6.9> =
set_placeholder_to("TEMPLATE", req->details1, 0); char *t = read_placeholder("TEMPLATE"); if (use_css_code_styles) { char *from = find_file_in_named_template(t, "style.css"); if (from) { char CSS_filename[MAX_FILENAME_LENGTH]; sprintf(CSS_filename, "%s%cstyle.css", release_folder, SEP_CHAR); copy_file(from, CSS_filename, FALSE); } } release_file_into_website("index.html", t, NULL); request *req; LOOP_OVER(req, request) if (req->private == FALSE) switch (req->what_is_requested) { case INTERPRETER_REQ: release_file_into_website("play.html", t, NULL); break; case SOURCE_REQ: set_placeholder_to("SOURCEPREFIX", "source", 0); char source_text[MAX_FILENAME_LENGTH]; sprintf(source_text, "%s%cSource%cstory.ni", project_folder, SEP_CHAR, SEP_CHAR); set_placeholder_to("SOURCELOCATION", source_text, 0); release_file_into_website("source.html", t, NULL); break; } <Add further material as requested by the template 6.9.1>;
This code is used in §6.
<Add further material as requested by the template 6.9.1> =
char *from = find_file_in_named_template(t, "(extras).txt"); if (from) { i.e., if the "(extras).txt" file exists file_read(from, "can't open (extras) file", FALSE, read_requested_file, 0); }
This code is used in §6.9.
void read_requested_file(char *filename, text_file_position *tfp) { filename = trim_white_space(filename); if (filename[0] == 0) return; release_file_into_website(filename, read_placeholder("TEMPLATE"), NULL); }
The function read_requested_file is used in §6.9.1.
However, this is more expressive than the "(extras).txt" file because it
also has the ability to set placeholders in cblorb
. We use this mechanism
because it allows each interpreter to provide some metadata about its own
identity and exactly how it wants to be interfaced with the website which
cblorb
will generate. This isn't the place to document what those metadata
placeholders are and what they mean, since (except for a consistency check
below) cblorb
doesn't know anything about them — it's the Standard
website template which they need to match up to. Anyway, the best way
to get an idea of this is to read the manifest file for the default,
Parchment, interpreter.
char current_placeholder[MAX_VAR_NAME_LENGTH]; int cp_written = FALSE; void read_requested_ifile(char *manifestline, text_file_position *tfp) { if (cp_written == FALSE) { cp_written = TRUE; current_placeholder[0] = 0; } manifestline = trim_white_space(manifestline); if (manifestline[0] == '[') <Go into or out of placeholder setting mode 8.1>; if (current_placeholder[0] == 0) <We're outside placeholder mode, so it's a comment or a manifested filename 8.2> else <We're inside placeholder mode, so it's content to be spooled into the named placeholder 8.3>; }
The function read_requested_ifile is used in §6.8.
§8.1. Placeholders are set thus:
[INTERPRETERVERSION]
Parchment for Inform 7
[]
where the opening line names the placeholder, then one or more lines give
the contents, and the box line ends the definition.
We're in the mode if current_placeholder
is a non-empty C string, and
if so, then it's the name of the one being set. Thus the code to handle
the opening and closing lines can be identical.
<Go into or out of placeholder setting mode 8.1> =
if (manifestline[cblorb_strlen(manifestline)-1] == ']') { if (cblorb_strlen(manifestline) >= MAX_VAR_NAME_LENGTH) { error_1("placeholder name too long in manifest file", manifestline); return; } strcpy(current_placeholder, manifestline+1); current_placeholder[cblorb_strlen(current_placeholder)-1] = 0; return; } error_1("placeholder name lacks ']' in manifest file", manifestline); return;
This code is used in §8.
<We're outside placeholder mode, so it's a comment or a manifested filename 8.2> =
if ((manifestline[0] == '!') || (manifestline[0] == 0)) return; release_file_into_website(manifestline, read_placeholder("INTERPRETER"), "interpreter");
This code is used in §8.
<We're inside placeholder mode, so it's content to be spooled into the named placeholder 8.3> =
if (strcmp(current_placeholder, "INTERPRETERVM") == 0) <Check the value being given against the actual VM we're blorbing up 8.3.1>; if (read_placeholder(current_placeholder)) append_to_placeholder(current_placeholder, "\n"); append_to_placeholder(current_placeholder, manifestline);
This code is used in §8.
<Check the value being given against the actual VM we're blorbing up 8.3.1> =
char *vm_used = read_placeholder("INTERPRETERVMIS"); int i, capable = FALSE; for (i=0; manifestline[i]; i++) if (vm_used[0] == manifestline[i]) capable = TRUE; if (capable == FALSE) { char *format = "Z-machine"; if (vm_used[0] == 'g') format = "Glulx"; errorf_2s( "You asked to release along with a copy of the '%s' in-browser " "interpreter, but this can't handle story files which use the " "%s story file format. (The format can be changed on Inform's " "Settings panel for a project.)", read_placeholder("INTERPRETER"), format); }
This code is used in §8.3.
void release_file_into_website(char *name, char *t, char *sub) { char write_to[MAX_FILENAME_LENGTH]; if (sub) sprintf(write_to, "%s%c%s%c%s", release_folder, SEP_CHAR, sub, SEP_CHAR, name); else sprintf(write_to, "%s%c%s", release_folder, SEP_CHAR, name); char *from = find_file_in_named_template(t, name); if (from == NULL) { error_1("unable to find file in website template", name); return; } if (strcmp(get_filename_extension(name), ".html") == 0) <Release an HTML page from the template into the website 9.1> else <Release a binary file from the template into the website 9.2>; }
The function release_file_into_website is used in §6.6, §6.9, §7, §8.2.
<Release an HTML page from the template into the website 9.1> =
set_placeholder_to("TEMPLATE", t, 0); if (trace_mode) printf("! Web page %s from template %s\n", name, t); if (strcmp(name, "source.html") == 0) web_copy_source(from, release_folder); else web_copy(from, write_to);
This code is used in §9.
§9.2.
<Release a binary file from the template into the website 9.2> =
if (trace_mode) printf("! Binary file %s from template %s\n", name, t); copy_file(from, write_to, FALSE);
This code is used in §9.
void add_links_to_requested_resources(FILE *COPYTO) { request *req; LOOP_OVER(req, request) if (req->private == FALSE) switch (req->what_is_requested) { case WEBSITE_REQ: break; case INTERPRETER_REQ: fprintf(COPYTO, "<li>"); download_link(COPYTO, "Play In-Browser", NULL, "play.html", "link"); fprintf(COPYTO, "</li>"); break; case SOURCE_REQ: fprintf(COPYTO, "<li>"); download_link(COPYTO, "Source Text", NULL, "source.html", "link"); fprintf(COPYTO, "</li>"); break; case SOLUTION_REQ: fprintf(COPYTO, "<li>"); download_link(COPYTO, "Solution", NULL, "solution.txt", "link"); fprintf(COPYTO, "</li>"); break; case IFICTION_REQ: fprintf(COPYTO, "<li>"); download_link(COPYTO, "Library Card", NULL, "iFiction.xml", "link"); fprintf(COPYTO, "</li>"); break; } }
The function add_links_to_requested_resources is used in 3/links (§3).
void declare_where_blorb_should_be_copied(char *path) { char *leaf = read_placeholder("STORYFILE"); if (leaf == NULL) leaf = "Story"; printf("Copy blorb to: [[%s%c%s]]\n", path, SEP_CHAR, leaf); }
The function declare_where_blorb_should_be_copied is used in §6.
void report_requested_material(char *ph) { if (release_folder[0] == 0) return; this should never happen int launch_website = FALSE, launch_play = FALSE; append_to_placeholder(ph, "<ul>"); <Itemise the blorb file, possibly mentioning pictures and sounds 12.1>; <Itemise the website, mentioning how many pages it has 12.2>; <Itemise the interpreter 12.3>; <Itemise the library card 12.4>; <Itemise the solution file 12.5>; <Itemise the source text 12.6>; <Itemise auxiliary files in a sub-list 12.7>; append_to_placeholder(ph, "</ul>"); if ((launch_website) || (launch_play)) <Give a centred line of links to the main web pages produced 12.8>; <Add in links to release instructions from Inform source text 12.9>; <Add in advertisements for features Inform would like to offer 12.10>; }
The function report_requested_material is used in 1/main (§11.1).
§12.1.
<Itemise the blorb file, possibly mentioning pictures and sounds 12.1> =
if ((no_pictures_included > 1) || (no_sounds_included > 0)) append_to_placeholder(ph, "<li>The blorb file <b>[STORYFILE]</b> ([BLORBFILESIZE]K in size, " "including [BLORBFILEPICTURES] figures(s) and [BLORBFILESOUNDS] " "sound(s))</li>"); else append_to_placeholder(ph, "<li>The blorb file <b>[STORYFILE]</b> ([BLORBFILESIZE]K in size)</li>");
This code is used in §12.
§12.2.
<Itemise the website, mentioning how many pages it has 12.2> =
if (count_requests_of_type(WEBSITE_REQ) > 0) { append_to_placeholder(ph, "<li>A website (generated from the [TEMPLATE] template) of "); char pcount[32]; sprintf(pcount, "%d page%s", HTML_pages_created, (HTML_pages_created!=1)?"s":""); append_to_placeholder(ph, pcount); append_to_placeholder(ph, "</li>"); launch_website = TRUE; }
This code is used in §12.
§12.3.
<Itemise the interpreter 12.3> =
if (count_requests_of_type(INTERPRETER_REQ) > 0) { launch_play = TRUE; append_to_placeholder(ph, "<li>A play-in-browser page (generated from the [INTERPRETER] interpreter)</li>"); }
This code is used in §12.
§12.4.
<Itemise the library card 12.4> =
if (count_requests_of_type(IFICTION_REQ) > 0) append_to_placeholder(ph, "<li>The library card (stored as an iFiction record)</li>");
This code is used in §12.
§12.5.
<Itemise the solution file 12.5> =
if (count_requests_of_type(SOLUTION_REQ) > 0) append_to_placeholder(ph, "<li>A solution file</li>");
This code is used in §12.
§12.6.
<Itemise the source text 12.6> =
if (count_requests_of_type(SOURCE_REQ) > 0) { if (source_HTML_pages_created > 0) { append_to_placeholder(ph, "<li>The source text (as plain text and as "); char pcount[32]; sprintf(pcount, "%d web page%s", source_HTML_pages_created, (source_HTML_pages_created!=1)?"s":""); append_to_placeholder(ph, pcount); append_to_placeholder(ph, ")</li>"); } } if (count_requests_of_type(RELEASE_SOURCE_REQ) > 0) append_to_placeholder(ph, "<li>The source text (as part of the website)</li>");
This code is used in §12.
§12.7.
<Itemise auxiliary files in a sub-list 12.7> =
if (count_requests_of_type(COPY_REQ) > 0) { append_to_placeholder(ph, "<li>The following additional file(s):<ul>"); request *req; LOOP_OVER(req, request) if (req->what_is_requested == COPY_REQ) { char *leafname = req->details2; append_to_placeholder(ph, "<li>"); append_to_placeholder(ph, leafname); if (req->outcome_data >= 4096) { char filesize[32]; sprintf(filesize, " (%dK)", req->outcome_data/1024); append_to_placeholder(ph, filesize); } else if (req->outcome_data >= 0) { char filesize[32]; sprintf(filesize, " (%d byte%s)", req->outcome_data, (req->outcome_data!=1)?"s":""); append_to_placeholder(ph, filesize); } if (strcmp(req->details3, "--") != 0) { append_to_placeholder(ph, " to subfolder "); append_to_placeholder(ph, req->details3); } append_to_placeholder(ph, "</li>"); } append_to_placeholder(ph, "</ul></li>"); }
This code is used in §12.
<Give a centred line of links to the main web pages produced 12.8> =
append_to_placeholder(ph, "<p><center>"); if (launch_website) { append_to_placeholder(ph, "<a href=\"[JAVASCRIPTPRELUDE]" "openUrl('file://[**MATERIALSFOLDERPATHOPEN]/Release/index.html')\">" "<img src='inform:/outcome_images/browse.png' border=0></a> home page"); } if ((launch_website) && (launch_play)) append_to_placeholder(ph, " : "); if (launch_play) { append_to_placeholder(ph, "<a href=\"[JAVASCRIPTPRELUDE]" "openUrl('file://[**MATERIALSFOLDERPATHOPEN]/Release/play.html')\">" "<img src='inform:/outcome_images/browse.png' border=0></a> play-in-browser page"); } append_to_placeholder(ph, "</center></p>");
This code is used in §12.
<Add in links to release instructions from Inform source text 12.9> =
request *req; int count = 0; LOOP_OVER(req, request) if (req->what_is_requested == INSTRUCTION_REQ) { if (count == 0) append_to_placeholder(ph, "<p>The source text gives release instructions "); else append_to_placeholder(ph, " and "); append_to_placeholder(ph, req->details1); append_to_placeholder(ph, " here"); count++; } if (count > 0) append_to_placeholder(ph, ".</p>");
This code is used in §12.
<Add in advertisements for features Inform would like to offer 12.10> =
request *req; int count = 0; LOOP_OVER(req, request) if (req->what_is_requested == ALTERNATIVE_REQ) { if (count == 0) append_to_placeholder(ph, "<p>Here are some other possibilities you might want to consider:<p><ul>"); append_to_placeholder(ph, "<li>"); append_to_placeholder(ph, req->details1); append_to_placeholder(ph, "</li>"); count++; } if (count > 0) append_to_placeholder(ph, "</ul></p>");
This code is used in §12.
§13. A convenient way to see if we've received requests of any given type:
int count_requests_of_type(int t) { request *req; int count = 0; LOOP_OVER(req, request) if (req->what_is_requested == t) count++; return count; }
The function count_requests_of_type is used in §12.2, §12.3, §12.4, §12.5, §12.6, §12.7.
Purpose: To make a solution (.sol) file accompanying a release, if requested.
Definitions:
We will need to parse the entire skein into a tree structure, in which each
node (including leaves) is one of the following structures. We expect the
Inform user to have annotated certain nodes with the text ***
(three
asterisks); the solution file will ignore all paths in the skein which do
not lead to one of these ***
nodes. The surviving nodes, those in lines
which do lead to ***
endings, are called "relevant".
Some knots have "branch descriptions", others do not. These are the
options where choices have to be made. The branch_parent
and branch_count
fields are used to keep these labels: see below.
define MAX_NODE_ID_LENGTH 32 define MAX_COMMAND_LENGTH 128 define MAX_ANNOTATION_LENGTH 128
typedef struct skein_node { char id[MAX_NODE_ID_LENGTH]; uniquely identifying ID used within the Skein file char command[MAX_COMMAND_LENGTH]; text of the command at this node char annotation[MAX_ANNOTATION_LENGTH]; text of any annotation added by the user int relevant; is this node within one of the "relevant" lines in the skein? struct skein_node *branch_parent; the trunk of the branch description, if any, is this way int branch_count; the leaf of the branch description, if any, is this number struct skein_node *parent; within the Skein tree:NULL
for the root only struct skein_node *child; within the Skein tree:NULL
if a leaf struct skein_node *sibling; within the Skein tree:NULL
if the final option from its parent MEMORY_MANAGEMENT } skein_node;
The structure skein_node is private to this section.
¶2. The root of the Skein, representing the start position before any command is typed, lives here:
skein_node *root_skn = NULL; only NULL
when the tree is empty
Our method works in four steps. Steps 1 to 3 have a running time of $O(K^2)$, where $K$ is the number of knots in the Skein, and step 4 is $O(K\log_2(K))$, so the process as a whole is $O(K^2)$.
void walkthrough(char *Skein_filename, char *walkthrough_filename) { build_skein_tree(Skein_filename); if (root_skn == NULL) { error("there appear to be no threads in the Skein"); return; } identify_relevant_lines(); if (root_skn->relevant == FALSE) { error("no threads in the Skein have been marked '***'"); return; } prune_irrelevant_lines(); write_solution_file(walkthrough_filename); }
The function walkthrough is used in 3/rel (§6, §6.1).
§4. Step 1: building the Skein tree.
skein_node *current_skein_node = NULL; void build_skein_tree(char *Skein_filename) { root_skn = NULL; current_skein_node = NULL; file_read(Skein_filename, "can't open skein file", FALSE, read_skein_pass_1, 0); current_skein_node = NULL; file_read(Skein_filename, "can't open skein file", FALSE, read_skein_pass_2, 0); } void read_skein_pass_1(char *line, text_file_position *tfp) { read_skein_line(line, 1); } void read_skein_pass_2(char *line, text_file_position *tfp) { read_skein_line(line, 2); }
The function build_skein_tree is used in §3.
The function read_skein_pass_1 appears nowhere else.
The function read_skein_pass_2 appears nowhere else.
void read_skein_line(char *line, int pass) { char node_id[MAX_NODE_ID_LENGTH]; find_node_ID_in_tag(line, "item", node_id, MAX_NODE_ID_LENGTH, TRUE); if (pass == 1) { if (node_id[0]) <Create a new skein tree node with this node ID 5.1>; if (current_skein_node) { <Look for a "command" tag and set the command text from it 5.3>; <Look for an "annotation" tag and set the annotation text from it 5.4>; } } else { if (node_id[0]) current_skein_node = find_node_with_ID(node_id); if (current_skein_node) { char child_node_id[MAX_NODE_ID_LENGTH]; find_node_ID_in_tag(line, "child", child_node_id, MAX_NODE_ID_LENGTH, TRUE); if (child_node_id[0]) { skein_node *new_child = find_node_with_ID(child_node_id); if (new_child == NULL) { error("the skein file is malformed (B)"); return; } <Make the parent-child relationship 5.2>; } } } }
The function read_skein_line is used in §4.
§5.1. Note that the root is the first knot in the Skein file.
<Create a new skein tree node with this node ID 5.1> =
current_skein_node = CREATE(skein_node); if (root_skn == NULL) root_skn = current_skein_node; strcpy(current_skein_node->id, node_id); strcpy(current_skein_node->command, ""); strcpy(current_skein_node->annotation, ""); current_skein_node->branch_count = -1; current_skein_node->branch_parent = NULL; current_skein_node->parent = NULL; current_skein_node->child = NULL; current_skein_node->sibling = NULL; current_skein_node->relevant = FALSE; if (trace_mode) printf("Creating knot with ID '%s'\n", node_id);
This code is used in §5.
§5.2. We make new_child
the youngest child of current_skein_mode
:
<Make the parent-child relationship 5.2> =
new_child->parent = current_skein_node; if (current_skein_node->child == NULL) { current_skein_node->child = new_child; } else { skein_node *familial = current_skein_node->child; while (familial->sibling) familial = familial->sibling; familial->sibling = new_child; }
This code is used in §5.
§5.3.
<Look for a "command" tag and set the command text from it 5.3> =
char *p = current_skein_node->command; if (find_text_of_tag(line, "command", p, MAX_COMMAND_LENGTH, FALSE)) { if (trace_mode) printf("Raw command '%s'\n", p); undo_XML_escapes_in_string(p); convert_string_to_upper_case(p); if (trace_mode) printf("Processed command '%s'\n", p); }
This code is used in §5.
§5.4.
<Look for an "annotation" tag and set the annotation text from it 5.4> =
char *p = current_skein_node->annotation; if (find_text_of_tag(line, "annotation", p, MAX_ANNOTATION_LENGTH, FALSE)) { if (trace_mode) printf("Raw annotation '%s'\n", p); undo_XML_escapes_in_string(p); if (trace_mode) printf("Processed annotation '%s'\n", p); }
This code is used in §5.
§6. Try to find a node ID element attached to a particular tag on the line:
int find_node_ID_in_tag(char *line, char *tag, char *write_to, int max_length, int abort_not_trim) { char portion1[MAX_TEXT_FILE_LINE_LENGTH], portion2[MAX_TEXT_FILE_LINE_LENGTH]; char prototype[64]; strcpy(prototype, "%[^<]<"); strcat(prototype, tag); strcat(prototype, " nodeId=\"%[^\"]\""); write_to[0] = 0; if (sscanf(line, prototype, portion1, portion2) == 2) { if ((cblorb_strlen(portion2) >= max_length-1) && (abort_not_trim)) { error("the skein file is malformed (C)"); return FALSE; } strncpy(write_to, portion2, (size_t) max_length-1); write_to[max_length-1] = 0; return TRUE; } return FALSE; }
The function find_node_ID_in_tag is used in §5.
§7. Try to find the text of a particular tag on the line:
int find_text_of_tag(char *line, char *tag, char *write_to, int max_length, int abort_not_trim) { char portion1[MAX_TEXT_FILE_LINE_LENGTH], portion2[MAX_TEXT_FILE_LINE_LENGTH], portion3[MAX_TEXT_FILE_LINE_LENGTH]; char prototype[64]; strcpy(prototype, "%[^>]>%[^<]</"); strcat(prototype, tag); strcat(prototype, "%s"); if (sscanf(line, prototype, portion1, portion2, portion3) == 3) { if ((cblorb_strlen(portion2) >= max_length-1) && (abort_not_trim)) { error("the skein file is malformed (C)"); return FALSE; } strncpy(write_to, portion2, (size_t) max_length-1); write_to[max_length-1] = 0; if (trace_mode) printf("found %s = '%s'\n", tag, portion2); return TRUE; } return FALSE; }
The function find_text_of_tag is used in §5.3, §5.4.
§8. This is not very efficient, but:
skein_node *find_node_with_ID(char *id) { skein_node *skn; LOOP_OVER(skn, skein_node) if (strcmp(id, skn->id) == 0) return skn; return NULL; }
The function find_node_with_ID is used in §5.
§9. Finally, we needed the following string hackery:
void convert_string_to_upper_case(char *p) { int i; for (i=0; p[i]; i++) p[i]=cblorb_toupper(p[i]); }
The function convert_string_to_upper_case is used in §5.3.
void undo_XML_escapes_in_string(char *p) { int i = 0, j = 0; while (p[i]) { if (p[i] == '&') { char xml_escape[16]; int k=0; while ((p[i+k] != 0) && (p[i+k] != ';') && (k<14)) { xml_escape[k] = cblorb_tolower(p[i+k]); k++; } xml_escape[k] = p[i+k]; k++; xml_escape[k] = 0; <We have identified an XML escape 10.1>; } p[j++] = p[i++]; } p[j++] = 0; }
The function undo_XML_escapes_in_string is used in §5.3, §5.4.
§10.1. Note that all other ampersand-escapes are passed through verbatim.
<We have identified an XML escape 10.1> =
char c = 0; if (strcmp(xml_escape, "<") == 0) c = '<'; if (strcmp(xml_escape, ">") == 0) c = '>'; if (strcmp(xml_escape, "&") == 0) c = '&'; if (strcmp(xml_escape, "'") == 0) c = '\''; if (strcmp(xml_escape, """) == 0) c = '\"'; if (c) { p[j++] = c; i += cblorb_strlen(xml_escape); continue; }
This code is used in §10.
void identify_relevant_lines(void) { skein_node *skn; LOOP_OVER(skn, skein_node) { char *p = skn->annotation; if (trace_mode) printf("Knot %s is annotated '%s'\n", skn->id, p); if ((p[0] == '*') && (p[1] == '*') && (p[2] == '*')) { int i = 3, j; while (p[i] == ' ') i++; for (j=0; p[i]; i++) p[j++] = p[i]; p[j] = 0; skein_node *knot; for (knot = skn; knot; knot = knot->parent) { knot->relevant = TRUE; if (trace_mode) printf("Knot %s is relevant\n", knot->id); } } } }
The function identify_relevant_lines is used in §3.
void prune_irrelevant_lines(void) { skein_node *skn; LOOP_OVER(skn, skein_node) if ((skn->relevant == FALSE) && (skn->parent)) <Delete this node from its parent 12.1>; }
The function prune_irrelevant_lines is used in §3.
§12.1.
<Delete this node from its parent 12.1> =
if (skn->parent->child == skn) { skn->parent->child = skn->sibling; } else { skein_node *skn2 = skn->parent->child; while ((skn2) && (skn2->sibling != skn)) skn2 = skn2->sibling; if ((skn2) && (skn2->sibling == skn)) skn2->sibling = skn->sibling; } skn->parent = NULL; skn->sibling = NULL;
This code is used in §12.
§13. Step 4: writing the solution file.
void write_solution_file(char *walkthrough_filename) { FILE *SOL = fopen(walkthrough_filename, "w"); if (SOL == NULL) fatal_fs("unable to open destination for solution text file", walkthrough_filename); fprintf(SOL, "Solution to \""); copy_placeholder_to("TITLE", SOL); fprintf(SOL, "\" by "); copy_placeholder_to("AUTHOR", SOL); fprintf(SOL, "\n\n"); recursively_solve(SOL, root_skn, NULL); fclose(SOL); }
The function write_solution_file is used in §3.
void recursively_solve(FILE *SOL, skein_node *skn, skein_node *last_branch) { <Follow the skein down until we reach a divergence, if we do 14.1>; <Print the various alternatives from this knot where the threads diverge 14.2>; <Show the solutions down each of these alternative lines in turn 14.3>; }
The function recursively_solve is used in §13, §14.3.
<Follow the skein down until we reach a divergence, if we do 14.1> =
while ((skn->child == NULL) || (skn->child->sibling == NULL)) { if (skn->child == NULL) return; if (skn->child->sibling == NULL) { skn = skn->child; write_command(SOL, skn, NORMAL_COMMAND); } }
This code is used in §14.
<Print the various alternatives from this knot where the threads diverge 14.2> =
fprintf(SOL, "Choice:\n"); int branch_counter = 1; skein_node *option; for (option = skn->child; option; option = option->sibling) if (option->child == NULL) { write_command(SOL, option, BRANCH_TO_END_COMMAND); } else { option->branch_count = branch_counter++; option->branch_parent = last_branch; write_command(SOL, option, BRANCH_TO_LINE_COMMAND); }
This code is used in §14.
§14.3.
<Show the solutions down each of these alternative lines in turn 14.3> =
skein_node *option; for (option = skn->child; option; option = option->sibling) if (option->child) { fprintf(SOL, "\nBranch ("); write_branch_name(SOL, option); fprintf(SOL, ")\n"); recursively_solve(SOL, option, option); }
This code is used in §14.
§15. Writing individual commands and branch descriptions.
define NORMAL_COMMAND 1 define BRANCH_TO_END_COMMAND 2 define BRANCH_TO_LINE_COMMAND 3
void write_command(FILE *SOL, skein_node *cmd_skn, int form) { if (form != NORMAL_COMMAND) fprintf(SOL, " "); fprintf(SOL, "%s", cmd_skn->command); if (form != NORMAL_COMMAND) { fprintf(SOL, " -> "); if (form == BRANCH_TO_LINE_COMMAND) { fprintf(SOL, "go to branch ("); write_branch_name(SOL, cmd_skn); fprintf(SOL, ")"); } else fprintf(SOL, "end"); } if (cmd_skn->annotation[0]) fprintf(SOL, " ... %s", cmd_skn->annotation); fprintf(SOL, "\n"); }
The function write_command is used in §14.1, §14.2.
void write_branch_name(FILE *SOL, skein_node *skn) { if (skn->branch_parent) { write_branch_name(SOL, skn->branch_parent); fprintf(SOL, "."); } fprintf(SOL, "%d", skn->branch_count); }
The function write_branch_name is used in §14.3, §15.
Purpose: To manage links to auxiliary files, and placeholder variables.
Definitions:
typedef struct auxiliary_file { char relative_URL[MAX_FILENAME_LENGTH]; char full_filename[MAX_FILENAME_LENGTH]; char aux_leafname[MAX_FILENAME_LENGTH]; char aux_subfolder[MAX_FILENAME_LENGTH]; char description[MAX_FILENAME_LENGTH]; char format[MAX_EXTENSION_LENGTH]; e.g., "jpg", "pdf" MEMORY_MANAGEMENT } auxiliary_file;
The structure auxiliary_file is accessed in 2/blorb and here.
void create_auxiliary_file(char *filename, char *description, char *subfolder) { auxiliary_file *aux = CREATE(auxiliary_file); strcpy(aux->description, description); strcpy(aux->full_filename, filename); char *ext = get_filename_extension(filename); char *leaf = get_filename_leafname(filename); if (ext[0] == '.') { strcpy(aux->relative_URL, filename); if (cblorb_strlen(ext + 1) >= MAX_EXTENSION_LENGTH - 1) { error("auxiliary file has overlong extension"); return; } strcpy(aux->format, ext + 1); int k; for (k=0; aux->format[k]; k++) aux->format[k] = cblorb_tolower(aux->format[k]); } else { strcpy(aux->format, "link"); sprintf(aux->relative_URL, "%s%cindex.html", filename, SEP_CHAR); } strcpy(aux->aux_leafname, leaf); strcpy(aux->aux_subfolder, subfolder); printf("! Auxiliary file: <%s> = <%s>\n", filename, description); }
The function create_auxiliary_file is used in 1/blurb (§7.2).
void expand_AUXILIARY_variable(FILE *COPYTO) { auxiliary_file *aux; LOOP_OVER(aux, auxiliary_file) { if (strcmp(aux->description, "--") != 0) { fprintf(COPYTO, "<li>"); download_link(COPYTO, aux->description, aux->full_filename, aux->aux_leafname, aux->format); fprintf(COPYTO, "</li>"); } } add_links_to_requested_resources(COPYTO); }
The function expand_AUXILIARY_variable is used in 3/place (§7).
void expand_DOWNLOAD_variable(FILE *COPYTO) { char target_pathname[MAX_FILENAME_LENGTH]; eventual pathname of Blorb file written sprintf(target_pathname, "%s%c%s", release_folder, SEP_CHAR, read_placeholder("STORYFILE")); download_link(COPYTO, "Story File", target_pathname, read_placeholder("STORYFILE"), "Blorb"); }
The function expand_DOWNLOAD_variable is used in 3/place (§7).
§5. Links. This routine, then, handles either kind of link.
void download_link(FILE *COPYTO, char *desc, char *filename, char *relative_url, char *form) { int size_up = TRUE; if (strcmp(form, "link") == 0) size_up = FALSE; fprintf(COPYTO, "<a href=\"%s\">%s</a> ", relative_url, desc); open_style(COPYTO, "filetype"); fprintf(COPYTO, "(%s", form); if (size_up) { long int size = -1L; if (strcmp(desc, "Story File") == 0) size = (long int) blorb_file_size; else size = file_size(filename); if (size != -1L) <Write a description of the rough file size 5.1> } fprintf(COPYTO, ")"); close_style(COPYTO, "filetype"); }
The function download_link is used in §3, §4, 3/rel (§10).
<Write a description of the rough file size 5.1> =
char *units = " bytes"; long int remainder = 0; if (size > 1024L) { remainder = size % 1024L; size /= 1024L; units = "KB"; } if (size > 1024L) { remainder = size % 1024L; size /= 1024L; units = "MB"; } if (size > 1024L) { remainder = size % 1024L; size /= 1024L; units = "GB"; } if (size > 1024L) { remainder = size % 1024L; size /= 1024L; units = "TB"; } fprintf(COPYTO, ", %d", (int) size); if ((size < 100L) && (remainder >= 103L)) fprintf(COPYTO, ".%d", (int) (remainder/103L)); fprintf(COPYTO, "%s", units);
This code is used in §5.
void expand_COVER_variable(FILE *COPYTO) { if (cover_exists) { char *format = "png"; if (cover_is_in_JPEG_format) format = "jpg"; fprintf(COPYTO, "<a href=\"Cover.%s\"><img src=\"Small Cover.%s\" border=\"1\" /></a>", format, format); } }
The function expand_COVER_variable is used in 3/place (§7).
void request_copy_of_auxiliaries(void) { auxiliary_file *aux; LOOP_OVER(aux, auxiliary_file) if (strcmp(aux->format, "link") != 0) { if (trace_mode) printf("! COPY <%s> as <%s>\n", aux->full_filename, aux->aux_leafname); request_copy(aux->full_filename, aux->aux_leafname, aux->aux_subfolder); } }
The function request_copy_of_auxiliaries is used in 3/rel (§5).
Purpose: To manage placeholder variables.
Definitions:
define SOURCE_RPL 1 define SOURCENOTES_RPL 2 define SOURCELINKS_RPL 3 define COVER_RPL 4 define DOWNLOAD_RPL 5 define AUXILIARY_RPL 6 define PAGENUMBER_RPL 7 define PAGEEXTENT_RPL 8
typedef struct placeholder {
char pl_name[MAX_VAR_NAME_LENGTH];
char pl_contents[MAX_FILENAME_LENGTH]; current value
int reservation; one of the *_RPL
values above, or 0 for unreserved
int locked; currently being expanded: locked to prevent mise-en-abyme
MEMORY_MANAGEMENT
} placeholder;
The structure placeholder is private to this section.
void initialise_placeholders(void) { set_placeholder_to("SOURCE", "", SOURCE_RPL); set_placeholder_to("SOURCENOTES", "", SOURCENOTES_RPL); set_placeholder_to("SOURCELINKS", "", SOURCELINKS_RPL); set_placeholder_to("COVER", "", COVER_RPL); set_placeholder_to("DOWNLOAD", "", DOWNLOAD_RPL); set_placeholder_to("AUXILIARY", "", AUXILIARY_RPL); set_placeholder_to("PAGENUMBER", "", PAGENUMBER_RPL); set_placeholder_to("PAGEEXTENT", "", PAGEEXTENT_RPL); set_placeholder_to("CBLORBERRORS", "", 0); set_placeholder_to("INBROWSERPLAY", "", 0); set_placeholder_to("INTERPRETERSCRIPTS", "", 0); set_placeholder_to("OTHERCREDITS", "", 0); set_placeholder_to("BLURB", "", 0); set_placeholder_to("TEMPLATE", "Standard", 0); set_placeholder_to("GENERATOR", VERSION, 0); set_placeholder_to("BASE64_TOP", "", 0); set_placeholder_to("BASE64_TAIL", "", 0); set_placeholder_to("JAVASCRIPTPRELUDE", JAVASCRIPT_PRELUDE, 0); set_placeholder_to("FONTTAG", FONT_TAG, 0); initialise_time_variables(); }
The function initialise_placeholders is used in 1/main (§7).
placeholder *find_placeholder(char *name) { placeholder *wv; LOOP_OVER(wv, placeholder) if (strcmp(wv->pl_name, name) == 0) return wv; return NULL; } char *read_placeholder(char *name) { placeholder *wv = find_placeholder(name); if (wv) return wv->pl_contents; return NULL; }
The function find_placeholder is used in §6, §7.
The function read_placeholder is used in 1/main (§10), 1/blurb (§7.2.2, §8), 3/rel (§5, §6.5, §6.8, §6.9, §7, §8.2, §8.3, §8.3.1, §11), 3/links (§4), 3/web (§11, §15, §15.1, §18).
void set_placeholder_to_number(char *var, int v) { char temp_digits[64]; sprintf(temp_digits, "%d", v); set_placeholder_to(var, temp_digits, 0); }
The function set_placeholder_to_number is used in 1/main (§9, §11.1), 1/blurb (§7.2).
void set_placeholder_to(char *var, char *text, int reservation) { set_placeholder_to_inner(var, text, reservation, FALSE); } void append_to_placeholder(char *var, char *text) { set_placeholder_to_inner(var, text, 0, TRUE); }
The function set_placeholder_to is used in §2, §4, 1/main (§9, §11.1), 1/blurb (§7.2, §7.2.1, §7.2.2), 3/rel (§6.7, §6.8, §6.9, §9.1).
The function append_to_placeholder is used in 1/text (§6), 1/blurb (§8), 3/rel (§8.3, §12, §12.1, §12.2, §12.3, §12.4, §12.5, §12.6, §12.7, §12.8, §12.9, §12.10).
void set_placeholder_to_inner(char *var, char *text, int reservation, int extend) { if (cblorb_strlen(var) >= MAX_VAR_NAME_LENGTH-1) { error("variable name too long"); return; } if (trace_mode) printf("! [%s] <-- \"%s\"\n", var, (text)?text:""); placeholder *wv = find_placeholder(var); if ((wv) && (reservation > 0)) { error("tried to set reserved variable"); return; } if (wv == NULL) { wv = CREATE(placeholder); if (trace_mode) printf("! Creating [%s]\n", var); strcpy(wv->pl_name, var); (wv->pl_contents)[0] = 0; wv->reservation = reservation; } int L = cblorb_strlen(text) + 1; if (extend) L += cblorb_strlen(wv->pl_contents); if (L >= MAX_FILENAME_LENGTH) { error("placeholder text too long"); return; } if (extend) strcat(wv->pl_contents, text); else strcpy(wv->pl_contents, text); }
The function set_placeholder_to_inner is used in §5.
If the placeholder name isn't known to us, we print the text back, so that the original material will be unchanged. (This is in case the original contains uses of square brackets which aren't for placeholding.)
int escape_quotes_mode = 0; void copy_placeholder_to(char *var, FILE *COPYTO) { int multiparagraph_mode = FALSE, eqm = escape_quotes_mode; if (var[0] == '*') { var++; escape_quotes_mode = 1; } if (var[0] == '*') { var++; escape_quotes_mode = 2; } if (strcmp(var, "BLURB") == 0) multiparagraph_mode = TRUE; placeholder *wv = find_placeholder(var); if ((wv == NULL) || (wv->locked)) { fprintf(COPYTO, "[%s]", var); } else { wv->locked = TRUE; if (multiparagraph_mode) fprintf(COPYTO, "<p>"); switch (wv->reservation) { case 0: <Copy an ordinary unreserved placeholder 7.1>; break; case SOURCE_RPL: expand_SOURCE_or_SOURCENOTES_variable(COPYTO, FALSE); break; case SOURCENOTES_RPL: expand_SOURCE_or_SOURCENOTES_variable(COPYTO, TRUE); break; case SOURCELINKS_RPL: expand_SOURCELINKS_variable(COPYTO); break; case COVER_RPL: expand_COVER_variable(COPYTO); break; case DOWNLOAD_RPL: expand_DOWNLOAD_variable(COPYTO); break; case AUXILIARY_RPL: expand_AUXILIARY_variable(COPYTO); break; case PAGENUMBER_RPL: expand_PAGENUMBER_variable(COPYTO); break; case PAGEEXTENT_RPL: expand_PAGEEXTENT_variable(COPYTO); break; } if (multiparagraph_mode) fprintf(COPYTO, "</p>"); wv->locked = FALSE; escape_quotes_mode = eqm; } }
The function copy_placeholder_to is used in §7.1, 3/sol (§13), 3/web (§10.1, §10.2).
<Copy an ordinary unreserved placeholder 7.1> =
int i; char *p = wv->pl_contents; for (i=0; p[i]; i++) { if ((p[i] == '<') && (p[i+1] == 'b') && (p[i+2] == 'r') && (p[i+3] == '/') && (p[i+4] == '>') && (multiparagraph_mode)) { fprintf(COPYTO, "</p><p>"); i += 4; continue; } if (p[i] == '[') { char inner_name[MAX_VAR_NAME_LENGTH+1]; int j = i+1, k = 0, expanded = FALSE; inner_name[0] = 0; for (; p[j]; j++) { if ((p[j] == '[') || (p[j] == ' ')) break; if (p[j] == ']') { i = j; copy_placeholder_to(inner_name, COPYTO); expanded = TRUE; break; } inner_name[k++] = p[j]; inner_name[k] = 0; if (k >= MAX_VAR_NAME_LENGTH) break; } if (expanded) continue; } if (((p[i] == '\x0a') || (p[i] == '\x0d') || (p[i] == '\x7f')) && (multiparagraph_mode)) { fprintf(COPYTO, "<p>"); continue; } if ((escape_quotes_mode == 1) && (p[i] == '\'')) fprintf(COPYTO, "'"); else if ((escape_quotes_mode == 2) && (p[i] == '\'')) fprintf(COPYTO, "%%2527"); else fprintf(COPYTO, "%c", p[i]); }
This code is used in §7.
Purpose: To manage templates for website generation.
Definitions:
¶1. Template paths define, in order of priority, where to look for templates.
typedef struct template_path { char template_repository[MAX_FILENAME_LENGTH]; pathname of folder of repository MEMORY_MANAGEMENT } template_path;
The structure template_path is private to this section.
¶2. Templates are the things themselves.
typedef struct template { char template_name[MAX_FILENAME_LENGTH]; e.g., "Standard" struct template_path *template_location; char latest_use[MAX_FILENAME_LENGTH]; filename most recently sought from it MEMORY_MANAGEMENT } template;
The structure template is private to this section.
§3. Defining template paths. The following implements the Blurb command "template path".
int no_template_paths = 0; void new_template_path(char *pathname) { template_path *tp = CREATE(template_path); strcpy(tp->template_repository, pathname); if (trace_mode) printf("! Template search path %d: <%s>\n", ++no_template_paths, pathname); }
The function new_template_path is used in 1/blurb (§7.2).
template_path *seek_file_in_template_paths(char *name, char *leafname) { template_path *tp; LOOP_OVER(tp, template_path) { char possible[MAX_FILENAME_LENGTH]; sprintf(possible, "%s%c%s%c%s", tp->template_repository, SEP_CHAR, name, SEP_CHAR, leafname); if (file_exists(possible)) return tp; } return NULL; }
The function seek_file_in_template_paths is used in §5.
template *find_template(char *name) { template *t; <Is this a template we already know? 5.1>; template_path *tp = seek_file_in_template_paths(name, "index.html"); if (tp == NULL) tp = seek_file_in_template_paths(name, "source.html"); if (tp == NULL) tp = seek_file_in_template_paths(name, "style.css"); if (tp == NULL) tp = seek_file_in_template_paths(name, "(extras).txt"); if (tp == NULL) tp = seek_file_in_template_paths(name, "(manifest).txt"); if (tp) { t = CREATE(template); strcpy(t->template_name, name); t->template_location = tp; return t; } return NULL; }
The function find_template is used in §6.
§5.1. It reduces pointless file accesses to cache the results, so:
<Is this a template we already know? 5.1> =
LOOP_OVER(t, template) if (strcmp(name, t->template_name) == 0) return t;
This code is used in §5.
int template_doesnt_exist = FALSE; char *find_file_in_named_template(char *name, char *needed) { template *t = find_template(name), *Standard = find_template("Standard"); if (t == NULL) { if (template_doesnt_exist == FALSE) { errorf_1s( "Websites and play-in-browser interpreter web pages are created " "using named templates. (Basic examples are built into the Inform " "application. You can also create your own, putting them in the " "'Templates' subfolder of the project's Materials folder.) Each " "template has a name. On this Release, I tried to use the " "'%s' template, but couldn't find a copy of it anywhere.", name); } template_doesnt_exist = TRUE; } char *path = try_single_template(t, needed); if ((path == NULL) && (Standard)) path = try_single_template(Standard, needed); return path; }
The function find_file_in_named_template is used in 3/rel (§6.7, §6.8, §6.9, §6.9.1, §9).
char *try_single_template(template *t, char *needed) { if (t == NULL) return NULL; sprintf(t->latest_use, "%s%c%s%c%s", t->template_location->template_repository, SEP_CHAR, t->template_name, SEP_CHAR, needed); if (trace_mode) printf("! Trying <%s>\n", t->latest_use); if (file_exists(t->latest_use)) return t->latest_use; return NULL; }
The function try_single_template is used in §6.
Purpose: To accompany a release with a mini-website.
Definitions:
define ABBREVIATED_HEADING_LENGTH 1000
typedef struct table { int table_line_start; line number in the source where the table heading appears int table_line_end; line number of the blank line which marks the end of the table body MEMORY_MANAGEMENT } table; typedef struct heading { int heading_line; line number in the source at which the heading appears int heading_level; a low number makes this a more significant heading than a high number int heading_has_content; is there anything other than white space before the next heading? struct segment *heading_to_segment; which segment contains the heading char heading_text[ABBREVIATED_HEADING_LENGTH + 1]; truncated if necessary for the contents MEMORY_MANAGEMENT } heading;
The structure table is private to this section.
The structure heading is private to this section.
It is not true that the source text is partitioned exactly by segments. The topmost segment begins at the first heading in the source text. So there will usually be at least a few prefatory lines before this point — perhaps the title, some extension inclusions, and so on — and it's even possible, if there are no headings at all, for there to be no segments so that the entire source text is "prefatory". If we have three segments, then, we will split the source text into four HTML files:
source0.html
— "Page 1 of 4", the preface and then contents source1.html
— "Page 2 of 4", first segment (with allocation ID 0) source2.html
— "Page 3 of 4", second segment (with allocation ID 1) source3.html
— "Page 4 of 4", third segment (with allocation ID 2)
Note that the prefatory lines contain no headings, that every heading
belongs to a unique segment (hence the heading_to_segment
field above)
and that the top line of every segment is always a heading. A single
segment can contain multiple headings, because we run on a heading if it
contains no content except white space: this is so that, e.g.,
Part I - Up the Amazon
Section I.1 - The lower delta
Rickety Jetty is a room. [...]
would be combined into a single segment, rather than a pointlessly short segment just containing the "Part I" heading followed by a second segment opening with "Section I.1".
typedef struct segment { int begins_at; line number on which the segment begins int ends_at; line number of the last line of the segment, orMAX_SOURCE_TEXT_LINES
if it runs to the end int documentation; is this in the documentation of an extension? struct text_file_position start_position_in_file; within the source text struct heading *most_recent_heading; orNULL
if there hasn't been one struct table *most_recent_table; orNULL
if there hasn't been one char segment_url[MAX_FILENAME_LENGTH]; char *link_home; char *link_contents; char *link_previous; char *link_next; int page_number; MEMORY_MANAGEMENT } segment;
The structure segment is private to this section.
columnhead
— the heading of a column in a Table in I7 source text comment
— comments in I7 source text filetype
— the "(pdf, 150KB)" text annotating links heading
— heading or top line of a Table in I7 source text i6code
— verbatim I6 code in I7 source text notecue
— footnote cues which annotate I7 source text notesheading
— the little "Notes" subheading above the footnotes to source text notetext
— texts of footnotes which annotate I7 source text quote
— double-quoted text in I7 source text substitution
— text substitution inside double-quoted text in I7 source text
In addition it must provide paragraph classes indent0
to indent9
for code
which begins at tab positions 0 to 9 (see below). Although "Standard.css"
contains other names of classes, these are only needed because "Standard.html"
or "Standard-Source.html" say so: cblorb
does not mandate them.
§4. In case CSS is not available, we use old-fashioned HTML alternatives:
void open_style(FILE *write_to, char *new) { if (new == NULL) return; if (use_css_code_styles) { fprintf(write_to, "<span class=\"%s\">", new); } else { if (strcmp(new, "columnhead") == 0) fprintf(write_to, "<u>"); if (strcmp(new, "comment") == 0) fprintf(write_to, "<font color=#404040>"); if (strcmp(new, "filetype") == 0) fprintf(write_to, "<small>"); if (strcmp(new, "heading") == 0) fprintf(write_to, "<b>"); if (strcmp(new, "i6code") == 0) fprintf(write_to, "<font color=#909090>"); if (strcmp(new, "notecue") == 0) fprintf(write_to, "<font color=#404040><sup>"); if (strcmp(new, "notesheading") == 0) fprintf(write_to, "<i>"); if (strcmp(new, "notetext") == 0) fprintf(write_to, "<font color=#404040>"); if (strcmp(new, "quote") == 0) fprintf(write_to, "<font color=#000080>"); if (strcmp(new, "substitution") == 0) fprintf(write_to, "<font color=#000080>"); } } void close_style(FILE *write_to, char *old) { if (old == NULL) return; if (use_css_code_styles) { fprintf(write_to, "</span>"); } else { if (strcmp(old, "columnhead") == 0) fprintf(write_to, "</u>"); if (strcmp(old, "comment") == 0) fprintf(write_to, "</font>"); if (strcmp(old, "filetype") == 0) fprintf(write_to, "</small>"); if (strcmp(old, "heading") == 0) fprintf(write_to, "</b>"); if (strcmp(old, "i6code") == 0) fprintf(write_to, "</font>"); if (strcmp(old, "notecue") == 0) fprintf(write_to, "</sup></font>"); if (strcmp(old, "notesheading") == 0) fprintf(write_to, "</i>"); if (strcmp(old, "notetext") == 0) fprintf(write_to, "</font>"); if (strcmp(old, "quote") == 0) fprintf(write_to, "</font>"); if (strcmp(old, "substitution") == 0) fprintf(write_to, "</font>"); } }
The function open_style is used in §5, §20.2, §22.3, §22.4.1, §22.4.7.3.1, 3/links (§5).
The function close_style is used in §5, §20.2, §22.3, §22.4.2, §22.4.7.3.1, 3/links (§5).
char *current_style = NULL; void change_style(FILE *write_to, char *new) { if (current_style) close_style(write_to, current_style); open_style(write_to, new); current_style = new; }
The function change_style is used in §22.4.7, §22.4.7.2, §22.4.7.3, §22.4.7.4, §22.4.7.5, §22.4.7.6.
The block of source text displayed on a web page should be framed within:
void open_code(FILE *write_to) { if (use_css_code_styles == FALSE) { fprintf(write_to, "<p>"); } } void close_code(FILE *write_to) { if (use_css_code_styles == FALSE) { fprintf(write_to, "</p>"); } }
The function open_code is used in §20.
The function close_code is used in §20.
void open_code_paragraph(FILE *write_to, int indentation) { if (use_css_code_styles) { char *classname = ""; switch (indentation) { case 0: classname = "indent0"; break; case 1: classname = "indent1"; break; case 2: classname = "indent2"; break; case 3: classname = "indent3"; break; case 4: classname = "indent4"; break; case 5: classname = "indent5"; break; case 6: classname = "indent6"; break; case 7: classname = "indent7"; break; case 8: classname = "indent8"; break; default: classname = "indent9"; break; } fprintf(write_to, "<p class=\"%s\">", classname); } else { int i; for (i=0; i<indentation; i++) fprintf(write_to, " "); } } void close_code_paragraph(FILE *write_to) { if (use_css_code_styles) { fprintf(write_to, "</p>"); } else { fprintf(write_to, "<br/>"); } }
The function open_code_paragraph is used in §22.4.
The function close_code_paragraph is used in §22.4.
void open_table_cell(FILE *write_to) { if (use_css_code_styles) { fprintf(write_to, "<td>"); } else { fprintf(write_to, "<td halign=\"left\" valign=\"top\">"); } } void close_table_cell(FILE *write_to) { if (use_css_code_styles) { fprintf(write_to, "</td>"); } else { fprintf(write_to, " </td>"); } }
The function open_table_cell is used in §22.4, §22.4.7.1.
The function close_table_cell is used in §22.4, §22.4.7.1.
§9. Making an HTML page from a template.
FILE *COPYTO = NULL; void web_copy(char *from, char *to) { if ((from == NULL) || (to == NULL) || (strcmp(from, to) == 0)) fatal("files confused in website maker"); HTML_pages_created++; COPYTO = fopen(to, "w"); if (COPYTO == NULL) { error_1("unable to open file to be written for web site", to); return; } file_read(from, "can't open template file", FALSE, copy_html_line, 0); fclose(COPYTO); }
The function web_copy is used in §15.3, §15.4, 1/main (§11), 3/rel (§9.1).
§10. Each line in turn comes here, then:
void copy_html_line(char *line, text_file_position *tfp) { int i; for (i=0; line[i]; i++) { <Detect square-bracketed names of Web variables and expand them 10.1>; <Detect an out of head experience 10.2>; fprintf(COPYTO, "%c", line[i]); } fprintf(COPYTO, "\n"); }
The function copy_html_line is used in §9.
§10.1.
<Detect square-bracketed names of Web variables and expand them 10.1> =
if (line[i] == '[') { int j; for (j=i+1; (line[j] && line[j]!=']'); j++) ; if (line[j] == ']') { line[j] = 0; copy_placeholder_to(line+i+1, COPYTO); line[j] = ']'; i = j; continue; } }
This code is used in §10.
<Detect an out of head experience 10.2> =
if ((line[i] == '<') && (line[i+1] == '/') && (line[i+2] == 'h') && (line[i+3] == 'e') && (line[i+4] == 'a') && (line[i+5] == 'd') && (line[i+6] == '>')) copy_placeholder_to("INTERPRETERSCRIPTS", COPYTO);
This code is used in §10.
This is done in two passes. On pass 1, we scan the source text for tables and headings, and divide the whole into "segments", each of which is typeset as a single HTML page: segments do not quite correspond to headings, as we shall see. But we write nothing. On pass 2, we actually write these HTML pages.
char source_text[MAX_FILENAME_LENGTH]; void web_copy_source(char *template, char *website_pathname) { strcpy(source_text, read_placeholder("SOURCELOCATION")); scan_source_text(); write_source_text_pages(template, website_pathname); }
The function web_copy_source is used in 3/rel (§6.7, §9.1).
int within_a_table; are we inside a Table declaration in the source text? int scan_quoted_matter; are we inside double-quoted matter in the source text? int scan_comment_nesting; level of nesting of comments in source text: 0 means "not in a comment" text_file_position *latest_line_position;ftell
-reported byte offset of the start of the current line in the source table *current_table; the Table which started most recently, orNULL
if none has heading *current_heading; the heading seen most recently, orNULL
if none has been segment *current_segment; the segment which started most recently, orNULL
if none has int position_of_documentation_bar; line count of the---- Documentation ----
line, if there is one
void scan_source_text(void) { within_a_table = FALSE; scan_comment_nesting = 0; scan_quoted_matter = FALSE; latest_line_position = NULL; current_table = NULL; current_heading = NULL; current_segment = NULL; position_of_documentation_bar = MAX_SOURCE_TEXT_LINES; file_read(source_text, "can't open source text of project", TRUE, scan_source_line, NULL); <Adjust heading levels downwards as far as we can without losing relative hierarchy 13.1>; }
The function scan_source_text is used in §11.
<Adjust heading levels downwards as far as we can without losing relative hierarchy 13.1> =
int minhl = 10; heading *h; LOOP_OVER(h, heading) if (h->heading_level < DOC_LEVEL) if (h->heading_level < minhl) minhl = h->heading_level; LOOP_OVER(h, heading) if (h->heading_level < DOC_LEVEL) h->heading_level -= minhl;
This code is used in §13.
void scan_source_line(char *line, text_file_position *tfp) { int lc = tfp_get_line_count(tfp), lv = DULL_LEVEL; latest_line_position = tfp; if (scan_quoted_matter == FALSE) <Look at the first word on the line to find the level of our interest 14.1>; if ((scan_comment_nesting > 0) && (lv != EMPTY_LEVEL)) lv = DULL_LEVEL; <Correct the comment nesting level ready for next time 14.2>; if ((lv == DULL_LEVEL) && (current_heading)) current_heading->heading_has_content = TRUE; if ((lv == EMPTY_LEVEL) && (within_a_table)) <End a table here and return 14.4>; if (lv == TABLE_LEVEL) <Start a new table here and return 14.3>; if ((lv == EMPTY_LEVEL) || (lv == DULL_LEVEL)) return; if (lv == DOC_LEVEL) position_of_documentation_bar = lc; <Place a new heading here 14.5>; }
The function scan_source_line is used in §13.
define EMPTY_LEVEL -1 define DULL_LEVEL 0 define TABLE_LEVEL 1000 define DOC_LEVEL 1001 define EXAMPLE_LEVEL 1002 define DOC_CHAPTER_LEVEL 1003 define DOC_SECTION_LEVEL 1004
<Look at the first word on the line to find the level of our interest 14.1> =
char fword[32]; extract_word(fword, line, 32, 1); if (fword[0] == 0) lv = EMPTY_LEVEL; if (strcmp(fword, "table") == 0) lv = TABLE_LEVEL; if (lc > position_of_documentation_bar) { if (strcmp(fword, "chapter:") == 0) lv = DOC_CHAPTER_LEVEL; if (strcmp(fword, "section:") == 0) lv = DOC_SECTION_LEVEL; if (strcmp(fword, "example:") == 0) lv = EXAMPLE_LEVEL; } else { if (strcmp(fword, "volume") == 0) lv = 1; if (strcmp(fword, "book") == 0) lv = 2; if (strcmp(fword, "part") == 0) lv = 3; if (strcmp(fword, "chapter") == 0) lv = 4; if (strcmp(fword, "section") == 0) lv = 5; if (strcmp(fword, "----") == 0) { extract_word(fword, line, 32, 2); if (strcmp(fword, "documentation") == 0) { extract_word(fword, line, 32, 3); if (strcmp(fword, "----") == 0) lv = DOC_LEVEL; } } }
This code is used in §14.
§14.2.
<Correct the comment nesting level ready for next time 14.2> =
int i; for (i=0; line[i]; i++) { if (line[i] == '[') scan_comment_nesting++; if (line[i] == ']') scan_comment_nesting--; if ((scan_comment_nesting == 0) && (line[i] == '\"')) scan_quoted_matter = (scan_quoted_matter)?FALSE:TRUE; }
This code is used in §14.
§14.3.
<Start a new table here and return 14.3> =
current_table = CREATE(table); current_table->table_line_start = lc; current_table->table_line_end = MAX_SOURCE_TEXT_LINES; within_a_table = TRUE; return;
This code is used in §14.
§14.4.
<End a table here and return 14.4> =
current_table->table_line_end = lc; within_a_table = FALSE; return;
This code is used in §14.
§14.5.
<Place a new heading here 14.5> =
heading *new_h = CREATE(heading); strncpy(new_h->heading_text, line, ABBREVIATED_HEADING_LENGTH); (new_h->heading_text)[ABBREVIATED_HEADING_LENGTH] = 0; new_h->heading_level = lv; new_h->heading_line = lc; new_h->heading_has_content = FALSE; if ((current_heading == NULL) || (current_heading->heading_has_content) || (lv == DOC_LEVEL)) { if (current_segment) current_segment->ends_at = lc - 1; current_segment = CREATE(segment); current_segment->begins_at = lc; current_segment->ends_at = MAX_SOURCE_TEXT_LINES; current_segment->start_position_in_file = *latest_line_position; current_segment->most_recent_heading = current_heading; current_segment->most_recent_table = current_table; current_segment->documentation = FALSE; if (lc >= position_of_documentation_bar) current_segment->documentation = TRUE; } new_h->heading_to_segment = current_segment; current_heading = new_h;
This code is used in §14.
segment *segment_being_written = NULL; int no_doc_files = 0, no_src_files = 0; void write_source_text_pages(char *template, char *website_pathname) { char contents_page[MAX_FILENAME_LENGTH]; sprintf(contents_page, "%s%c%s.html", website_pathname, SEP_CHAR, read_placeholder("SOURCEPREFIX")); char *contents_leafname = get_filename_leafname(contents_page); <Devise URLs for the segments 15.1>; <Work out how the segments link together 15.2>; <Generate the prefatory page, which isn't a segment 15.3>; <Generate the segment pages 15.4>; }
The function write_source_text_pages is used in §11.
<Devise URLs for the segments 15.1> =
segment *seg; LOOP_OVER(seg, segment) { segment_being_written = seg; if (seg->documentation) { sprintf(seg->segment_url, "doc_%d.html", no_doc_files++); seg->page_number = no_doc_files; } else { sprintf(seg->segment_url, "%s_%d.html", read_placeholder("SOURCEPREFIX"), no_src_files++); seg->page_number = no_src_files; } }
This code is used in §15.
§15.2.
<Work out how the segments link together 15.2> =
segment *seg, *first_doc_seg = NULL, *first_src_seg = NULL; LOOP_OVER(seg, segment) { if (seg->documentation) { seg->link_home = NULL; seg->link_contents = NULL; seg->link_previous = NULL; seg->link_next = NULL; if (first_doc_seg == NULL) first_doc_seg = seg; } else { seg->link_home = NULL; seg->link_contents = NULL; seg->link_previous = NULL; seg->link_next = NULL; if (first_src_seg == NULL) { first_src_seg = seg; seg->link_previous = contents_leafname; } } } LOOP_OVER(seg, segment) { if (seg->documentation) { seg->link_home = "index.html"; seg->link_contents = first_doc_seg->segment_url; } else { seg->link_home = "index.html"; seg->link_contents = contents_leafname; } segment *before = seg; while (TRUE) { before = PREV_OBJECT(before, segment); if (before == NULL) break; if (before->documentation == seg->documentation) { seg->link_previous = before->segment_url; break; } } segment *after = seg; while (TRUE) { after = NEXT_OBJECT(after, segment); if (after == NULL) break; if (after->documentation == seg->documentation) { seg->link_next = after->segment_url; break; } } }
This code is used in §15.
§15.3.
<Generate the prefatory page, which isn't a segment 15.3> =
segment_being_written = NULL; source_HTML_pages_created++; web_copy(template, contents_page);
This code is used in §15.
§15.4.
<Generate the segment pages 15.4> =
segment *seg; LOOP_OVER(seg, segment) { char segment_page[MAX_FILENAME_LENGTH]; sprintf(segment_page, "%s%c%s", website_pathname, SEP_CHAR, seg->segment_url); segment_being_written = seg; source_HTML_pages_created++; web_copy(template, segment_page); segment_being_written = NULL; }
This code is used in §15.
§16. This is what "[PAGENUMBER]" in the template becomes.
void expand_PAGENUMBER_variable(FILE *COPYTO) { int p = 1; if (segment_being_written) { p = segment_being_written->page_number; if (segment_being_written->documentation == FALSE) p++; allow for header page } fprintf(COPYTO, "%d", p); }
The function expand_PAGENUMBER_variable is used in 3/place (§7).
§17. And similarly "[PAGEEXTENT]".
void expand_PAGEEXTENT_variable(FILE *COPYTO) { int n = no_src_files + 1; if ((segment_being_written) && (segment_being_written->documentation)) n = no_doc_files; if (n == 0) n = 1; fprintf(COPYTO, "%d", n); }
The function expand_PAGEEXTENT_variable is used in 3/place (§7).
§18. And this is what "[SOURCELINKS]" in the template becomes:
void expand_SOURCELINKS_variable(FILE *COPYTO) { segment *seg = segment_being_written; if (seg) { if (seg->link_home) fprintf(COPYTO, "<li><a href=\"%s\">Home page</a></li>", seg->link_home); if (seg->link_contents) fprintf(COPYTO, "<li><a href=\"%s\">Beginning</a></li>", seg->link_contents); if (seg->link_previous) fprintf(COPYTO, "<li><a href=\"%s\">Previous</a></li>", seg->link_previous); if (seg->link_next) fprintf(COPYTO, "<li><a href=\"%s\">Next</a></li>", seg->link_next); } else { fprintf(COPYTO, "<li><a href=\"index.html\">Home page</a></li>"); fprintf(COPYTO, "<li><a href=\"%s.txt\">Complete text</a></li>", read_placeholder("SOURCEPREFIX")); } }
The function expand_SOURCELINKS_variable is used in 3/place (§7).
FILE *SPAGE = NULL; where the output is going int SOURCENOTES_mode = FALSE;TRUE
for "[SOURCENOTES]",FALSE
for "[SOURCE]" int quoted_matter = FALSE; are we inside double-quoted matter in the source text? int i6_matter = FALSE; are we inside verbatim I6 code in the source text? int comment_nesting = 0; nesting level of comments in source text being read: 0 for not in a comment int footnote_comment_level = 0; ditto, but where the outermost comment is a footnote marker int carry_over_indentation = -1; indentation carried over for para breaks in quoted text int next_footnote_number = 1; number to assign to the next footnote which comes up heading *latest_heading = NULL; a heading which is always behind the current position table *latest_table = NULL; a table which is always behind the current position
§20. So this is "[SOURCE]" (if noting_mode
is FALSE
) or "[SOURCENOTES]"
(if TRUE
).
void expand_SOURCE_or_SOURCENOTES_variable(FILE *write_to, int SN) { if (SN) <Typeset the little Notes subheading 20.2>; open_code(write_to); <Initialise the variables to their state at the start of an HTML page 20.1>; <Read the source text and feed it one line at a time to the line-writer 20.3>; close_code(write_to); }
The function expand_SOURCE_or_SOURCENOTES_variable is used in 3/place (§7).
§20.1. So at the start of the preface or of any segment:
<Initialise the variables to their state at the start of an HTML page 20.1> =
next_footnote_number = 1; SPAGE = write_to; SOURCENOTES_mode = SN; quoted_matter = FALSE; i6_matter = FALSE; comment_nesting = 0; footnote_comment_level = 0; carry_over_indentation = -1; current_style = NULL; latest_heading = FIRST_OBJECT(heading); latest_table = FIRST_OBJECT(table);
This code is used in §20.
<Typeset the little Notes subheading 20.2> =
if (next_footnote_number == 1) return; there were no footnotes at all fprintf(write_to, "<p>"); open_style(write_to, "notesheading"); if (next_footnote_number == 2) fprintf(write_to, "Note"); just one else fprintf(write_to, "Notes"); more than one close_style(write_to, "notesheading"); fprintf(write_to, "</p>\n");
This code is used in §20.
Instead, we start at the relevant position in the source text for the
current HTML page, and we stop the moment that write_source_line
reports
that it has gone past the material of interest. We thus make at most $N+H$
calls to write_source_line
(the extra $H$ calls being for one overspill line
per segment, where we realise that we've gone too far).
<Read the source text and feed it one line at a time to the line-writer 20.3> =
text_file_position *start = NULL; if (segment_being_written) <Start from just the right place in the source file 20.3.1>; file_read(source_text, "can't open source text", TRUE, source_write_iterator, start);
This code is used in §20.
<Start from just the right place in the source file 20.3.1> =
start = &(segment_being_written->start_position_in_file); if (segment_being_written->most_recent_heading) latest_heading = segment_being_written->most_recent_heading; if (segment_being_written->most_recent_table) latest_table = segment_being_written->most_recent_table;
This code is used in §20.3.
void source_write_iterator(char *line, text_file_position *tfp) { int done_yet = write_source_line(line, tfp); if (done_yet) tfp_lose_interest(tfp); }
The function source_write_iterator is used in §20.3.
When this routine returns TRUE
, it signals that there is no further need for
the source text, and that saves reading in all of the remaining lines which
won't be needed.
int write_source_line(char *line, text_file_position *tfp) { int line_count = tfp_get_line_count(tfp); if (segment_being_written == NULL) <Filter out lines for the preface 22.1> else <Filter out lines for the segments 22.2>; if (SOURCENOTES_mode) <Typeset the line in [SOURCENOTES] mode 22.3> else <Typeset the line in [SOURCE] mode 22.4>; return FALSE; }
The function write_source_line is used in §21.
Here we are handling the case of typesetting the preface. We allow the line
to appear as normal if it is before the first segment; once we reach the
first segment — if there's a first segment to reach — we then typeset the
contents listing. (If there's no first segment, then there are no headings,
and there's no need for a contents listing.) If we've output the contents
listing then we are finished writing the preface and don't need to read the
source text further, so we return TRUE
.
<Filter out lines for the preface 22.1> =
segment *first_segment = FIRST_OBJECT(segment); if ((first_segment) && (line_count == first_segment->begins_at - 1) && (line[0] == 0)) return FALSE; don't bother to typeset a blank line just before the first segment is reached if ((first_segment) && (line_count == first_segment->begins_at)) { if (SOURCENOTES_mode == FALSE) typeset_contents_listing(TRUE); return TRUE; }
This code is used in §22.
<Filter out lines for the segments 22.2> =
if (line_count < segment_being_written->begins_at) return FALSE; if (line_count > segment_being_written->ends_at) return TRUE; if (line_count == position_of_documentation_bar + 1) typeset_contents_listing(FALSE);
This code is used in §22.
<Typeset the line in [SOURCENOTES] mode 22.3> =
int i; for (i=0; line[i]; i++) { if ((line[i] == '[') && (line[i+1] == '*')) { footnote_comment_level = 1; fprintf(SPAGE, "<p><a name=\"note%d\"></a>", next_footnote_number); open_style(SPAGE, "notetext"); fprintf(SPAGE, "<a href=\"#note%dref\">[%d]</a>. ", next_footnote_number, next_footnote_number); next_footnote_number++; i+=2; } if (footnote_comment_level > 0) { if (line[i] == '[') footnote_comment_level++; if (line[i] == ']') footnote_comment_level--; if (footnote_comment_level == 0) { close_style(SPAGE, "notetext"); fprintf(SPAGE, "</p>\n"); } else { fprintf(SPAGE, "%c", line[i]); } } } if (footnote_comment_level > 0) fprintf(SPAGE, " ");
This code is used in §22.
<Typeset the line in [SOURCE] mode 22.4> =
int embolden = FALSE, tabulate = FALSE, underline = FALSE; <Decide any typographic embellishments due to the line falling inside a table 22.4.3>; <The top line of the preface or any segment is in bold 22.4.4>; <Any heading line is in bold 22.4.5>; if ((tabulate) && (quoted_matter == FALSE)) { fprintf(SPAGE, "<tr>"); open_table_cell(SPAGE); } int start = 0; if (footnote_comment_level > 0) { for (; line[start]; start++) { if (line[start] == '[') footnote_comment_level++; if (line[start] == ']') footnote_comment_level--; if (footnote_comment_level == 0) { start++; break; } } } if ((footnote_comment_level == 0) && (line[start])) { if (tabulate == FALSE) { int insteps = 0; for (; line[start] == '\t'; start++) insteps++; if (carry_over_indentation < 0) carry_over_indentation = insteps; open_code_paragraph(SPAGE, carry_over_indentation); } <Begin typographic embellishments 22.4.1>; <The documentation requires some corrections 22.4.6>; int i; for (i=start; line[i]; i++) <Typeset a single character of the source text 22.4.7>; <End typographic embellishments 22.4.2>; if ((tabulate) && (quoted_matter == FALSE)) { close_table_cell(SPAGE); fprintf(SPAGE, "</tr>\n"); } else close_code_paragraph(SPAGE); if (quoted_matter == FALSE) carry_over_indentation = -1; }
This code is used in §22.
<Begin typographic embellishments 22.4.1> =
if (underline) open_style(SPAGE, "columnhead"); if (embolden) open_style(SPAGE, "heading"); if (current_style) open_style(SPAGE, current_style);
This code is used in §22.4, §22.4.7.1.
§22.4.2. And they end in reverse order, so that they nest properly if need be:
<End typographic embellishments 22.4.2> =
if (current_style) close_style(SPAGE, current_style); if (embolden) close_style(SPAGE, "heading"); if (underline) close_style(SPAGE, "columnhead");
This code is used in §22.4, §22.4.7.1.
The while
loop here needs a careful look, since on the face of it this
could mean $O(N)$ iterations — since the number of tables is probably
proportional to $N$ — made in the course of the current "[SOURCE]"
expansion. Since the number of "[SOURCE]" expansions needed to make the
website is also $O(N)$ — the number of HTML pages in the site is proportional
to the number of headings, which is also proportional to $N$ — there's a
risk that this while
loop makes the whole website algorithm $O(N^2)$.
This is why, on each "[SOURCE]" expansion, latest_table
is initialised
not to the first table but to the most recent one at the start position of
the current HTML page. Moreover, the loop never goes past the current line
count, which never goes outside the range of lines in the current HTML page.
The result is that over the course of all the "[SOURCE]" expansions
combined, the while
loop here executes $O(N)$ iterations in total.
<Decide any typographic embellishments due to the line falling inside a table 22.4.3> =
while ((latest_table) && (latest_table->table_line_end < line_count)) latest_table = NEXT_OBJECT(latest_table, table); if (latest_table) { int from = latest_table->table_line_start, to = latest_table->table_line_end; if (line_count == from) { embolden = TRUE; } else if ((line_count > from) && (line_count < to)) { tabulate = TRUE; if (line_count == from + 1) { underline = TRUE; fprintf(SPAGE, "<table>"); } } else if (line_count == to) { fprintf(SPAGE, "</table>"); } }
This code is used in §22.4.
§22.4.4.
<The top line of the preface or any segment is in bold 22.4.4> =
if ((line_count == 1) || ((segment_being_written) && (line_count == segment_being_written->begins_at))) embolden = TRUE;
This code is used in §22.4.
<Any heading line is in bold 22.4.5> =
while ((latest_heading) && (latest_heading->heading_line < line_count)) latest_heading = NEXT_OBJECT(latest_heading, heading); if ((latest_heading) && (latest_heading->heading_line == line_count)) embolden = TRUE;
This code is used in §22.4.
§22.4.6.
<The documentation requires some corrections 22.4.6> =
if ((comment_nesting == 0) && (quoted_matter == FALSE) && (i6_matter == FALSE) && (line[start] == '*') && (line[start+1] == ':') && (line[start+2] == ' ')) start += 3; if (line_count == position_of_documentation_bar) strcpy(line, "Documentation");
This code is used in §22.4.
<Typeset a single character of the source text 22.4.7> =
switch (line[i]) { case '\t': a multiple tab is equivalent to a single tab in Inform source text while (line[i+1] == '\t') i++; <Typeset a tab 22.4.7.1>; break; case '"': if ((comment_nesting > 0) || (i6_matter)) fprintf(SPAGE, """); else <Typeset a double quotation mark outside of a comment 22.4.7.2>; break; case '[': if (quoted_matter) { fprintf(SPAGE, "["); change_style(SPAGE, "substitution"); } else if (i6_matter) fprintf(SPAGE, "["); else <Typeset an open square bracket outside of a string 22.4.7.3>; break; case ']': if (quoted_matter) { change_style(SPAGE, "quote"); fprintf(SPAGE, "]"); } else if (i6_matter) fprintf(SPAGE, "]"); else <Typeset a close square bracket outside of a string 22.4.7.4>; break; case '(': if ((comment_nesting == 0) && (quoted_matter == FALSE) && (i6_matter == FALSE) && (line[i+1] == '-')) { i++; <Typeset the opening of I6 verbatim code 22.4.7.5> } else fprintf(SPAGE, "("); break; case '-': if ((i6_matter) && (line[i+1] == ')')) { i++; <Typeset the closing of I6 verbatim code 22.4.7.6> } else fprintf(SPAGE, "-"); break; case '<': fprintf(SPAGE, "<"); break; case '>': fprintf(SPAGE, ">"); break; case '&': fprintf(SPAGE, "&"); break; default: fprintf(SPAGE, "%c", line[i]); break; }
This code is used in §22.4.
<Typeset a tab 22.4.7.1> =
if (tabulate) { <End typographic embellishments 22.4.2>; close_table_cell(SPAGE); open_table_cell(SPAGE); <Begin typographic embellishments 22.4.1>; } else { fprintf(SPAGE, " "); }
This code is used in §22.4.7.
Our code in handling quoted and comment matter is greatly simplified by the fact that a valid Inform text cannot contain mismatched square brackets; however, as Dave Chapeskie points out, a valid comment can contain mismatched quotation marks, and this section of code benefits from his careful amendments.
<Typeset a double quotation mark outside of a comment 22.4.7.2> =
if (quoted_matter) change_style(SPAGE, NULL); fprintf(SPAGE, """); if (quoted_matter == FALSE) change_style(SPAGE, "quote"); quoted_matter = (quoted_matter)?FALSE:TRUE;
This code is used in §22.4.7.
<Typeset an open square bracket outside of a string 22.4.7.3> =
if (line[i+1] == '*') { advance past the end of the asterisked comment footnote_comment_level++; for (i+=2; line[i]; ++i) { if (line[i] == '[') footnote_comment_level++; if (line[i] == ']') footnote_comment_level--; if (footnote_comment_level == 0) break; } if (line[i] == 0) i--; <Typeset a footnote cue 22.4.7.3.1>; } else { comment_nesting++; if (comment_nesting == 1) change_style(SPAGE, "comment"); fprintf(SPAGE, "["); }
This code is used in §22.4.7.
§22.4.7.4.
<Typeset a close square bracket outside of a string 22.4.7.4> =
fprintf(SPAGE, "]"); comment_nesting--; if (comment_nesting == 0) change_style(SPAGE, NULL);
This code is used in §22.4.7.
<Typeset the opening of I6 verbatim code 22.4.7.5> =
fprintf(SPAGE, "(-"); change_style(SPAGE, "i6code"); i6_matter = TRUE;
This code is used in §22.4.7.
§22.4.7.6.
<Typeset the closing of I6 verbatim code 22.4.7.6> =
change_style(SPAGE, NULL); fprintf(SPAGE, "-)"); i6_matter = FALSE;
This code is used in §22.4.7.
<Typeset a footnote cue 22.4.7.3.1> =
fprintf(SPAGE, "<a name=\"note%dref\"></a>", next_footnote_number); open_style(SPAGE, "notecue"); fprintf(SPAGE, "<a href=\"#note%d\">[%d]</a>", next_footnote_number, next_footnote_number); close_style(SPAGE, "notecue"); next_footnote_number++;
This code is used in §22.4.7.3.
void typeset_contents_listing(int source_contents) { int benchmark_level = (source_contents)?0:DOC_CHAPTER_LEVEL; int current_level = benchmark_level-1, new_level; heading *h; LOOP_OVER(h, heading) if (((source_contents) && (h->heading_line < position_of_documentation_bar)) || ((source_contents == FALSE) && (h->heading_line > position_of_documentation_bar))) { new_level = h->heading_level; if (h->heading_level == EXAMPLE_LEVEL) new_level = DOC_CHAPTER_LEVEL; <Open or close UL tags to move to the new heading level 23.1>; fprintf(SPAGE, "<li><a href=%s>%s</a></li>\n", h->heading_to_segment->segment_url, h->heading_text); } new_level = benchmark_level-1; <Open or close UL tags to move to the new heading level 23.1>; }
The function typeset_contents_listing is used in §22.1, §22.2.
<Open or close UL tags to move to the new heading level 23.1> =
while (new_level > current_level) { fprintf(SPAGE, "<ul>"); current_level++; } while (new_level < current_level) { fprintf(SPAGE, "</ul>"); current_level--; }
This code is used in §23 (twice).
Purpose: To produce base64-encoded story files ready for in-browser play by a Javascript-based interpreter such as Parchment.
RFC 1113 permits white space to be used freely, including in particular line breaks, but we don't avail ourselves.
char *RFC1113_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
§2. The encoding routine is as follows.
void encode_as_base64(char *in_filename, char *out_filename, char *top, char *tail) { FILE *IN = fopen(in_filename, "rb"); if (IN == NULL) fatal_fs("can't open story file for base-64 encoding", in_filename); FILE *OUT = fopen(out_filename, "w"); a text file, not binary if (OUT == NULL) fatal_fs("can't open base-64 encoded story file for output", out_filename); if (top) fprintf(OUT, "%s", top); while (TRUE) { int triplet[3], triplet_size = 0; <Read the triplet of binary bytes, storing 0 to 3 in the size read 2.1>; if (triplet_size == 0) break; int quartet[4]; <Convert triplet to a quartet 2.2>; int i; for (i=0; i<4; i++) fputc(RFC1113_table[quartet[i]], OUT); if (triplet_size < 3) break; } if (tail) fprintf(OUT, "%s", tail); fclose(IN); fclose(OUT); }
The function encode_as_base64 is used in 3/rel (§6.5).
§2.1. If the file ends in mid-triplet, we pad out with zeros.
<Read the triplet of binary bytes, storing 0 to 3 in the size read 2.1> =
triplet[0] = fgetc(IN); if (triplet[0] != EOF) { triplet_size++; triplet[1] = fgetc(IN); if (triplet[1] != EOF) { triplet_size++; triplet[2] = fgetc(IN); if (triplet[2] != EOF) triplet_size++; } } int i; for (i=triplet_size; i<3; i++) triplet[i] = 0;
This code is used in §2.
§2.2.
<Convert triplet to a quartet 2.2> =
int i; for (i=0; i<4; i++) quartet[i] = 0; quartet[0] += (triplet[0] & 0xFC) >> 2; quartet[1] += (triplet[0] & 0x03) << 4; quartet[1] += (triplet[1] & 0xF0) >> 4; quartet[2] += (triplet[1] & 0x0F) << 2; quartet[2] += (triplet[2] & 0xC0) >> 6; quartet[3] += (triplet[2] & 0x3F) << 0; switch (triplet_size) { case 1: quartet[2] = 64; quartet[3] = 64; break; case 2: quartet[3] = 64; break; }
This code is used in §2.