foundation-module: Chapter_8: Nowebify.

This commit is contained in:
AwesomeAdam54321 2024-03-09 11:58:05 +08:00
parent a15028316e
commit dea875195b
5 changed files with 227 additions and 223 deletions

View file

@ -3,11 +3,11 @@
To manage key-value pairs of bibliographic data, metadata if you like, To manage key-value pairs of bibliographic data, metadata if you like,
associated with a given web. associated with a given web.
@h Storing data. @ \section{Storing data.}
There are never more than a dozen or so key-value pairs, and it's more There are never more than a dozen or so key-value pairs, and it's more
convenient to store them directly here than to use a dictionary. convenient to store them directly here than to use a dictionary.
= <<*>>=
typedef struct web_bibliographic_datum { typedef struct web_bibliographic_datum {
struct text_stream *key; struct text_stream *key;
struct text_stream *value; struct text_stream *value;
@ -20,12 +20,13 @@ typedef struct web_bibliographic_datum {
@ We keep these in linked lists, and here's a convenient way to scan them: @ We keep these in linked lists, and here's a convenient way to scan them:
@d LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm) <<*>>=
#define LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm)
LOOP_OVER_LINKED_LIST(bd, web_bibliographic_datum, Wm->bibliographic_data) LOOP_OVER_LINKED_LIST(bd, web_bibliographic_datum, Wm->bibliographic_data)
@ The following check the rules: @ The following check the rules:
= <<*>>=
int Bibliographic::datum_can_be_declared(web_md *Wm, text_stream *key) { int Bibliographic::datum_can_be_declared(web_md *Wm, text_stream *key) {
web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key); web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key);
if (bd == NULL) return FALSE; if (bd == NULL) return FALSE;
@ -38,10 +39,10 @@ int Bibliographic::datum_on_or_off(web_md *Wm, text_stream *key) {
return bd->on_or_off; return bd->on_or_off;
} }
@h Initialising a web. @ \section{Initialising a web.}
Each web has the following slate of data: Each web has the following slate of data:
= <<*>>=
void Bibliographic::initialise_data(web_md *Wm) { void Bibliographic::initialise_data(web_md *Wm) {
web_bibliographic_datum *bd; web_bibliographic_datum *bd;
@ -82,7 +83,7 @@ void Bibliographic::initialise_data(web_md *Wm) {
@ Once the declarations for a web have been processed, the following is called @ Once the declarations for a web have been processed, the following is called
to check that all the mandatory declarations have indeed been made: to check that all the mandatory declarations have indeed been made:
= <<*>>=
void Bibliographic::check_required_data(web_md *Wm) { void Bibliographic::check_required_data(web_md *Wm) {
web_bibliographic_datum *bd; web_bibliographic_datum *bd;
LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm) LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm)
@ -92,10 +93,10 @@ void Bibliographic::check_required_data(web_md *Wm) {
"The web does not specify '%S: ...'", bd->key); "The web does not specify '%S: ...'", bd->key);
} }
@h Reading bibliographic data. @ \section{Reading bibliographic data.}
Key names are case-sensitive. Key names are case-sensitive.
= <<*>>=
text_stream *Bibliographic::get_datum(web_md *Wm, text_stream *key) { text_stream *Bibliographic::get_datum(web_md *Wm, text_stream *key) {
web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key); web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key);
if (bd) return bd->value; if (bd) return bd->value;
@ -118,20 +119,20 @@ web_bibliographic_datum *Bibliographic::look_up_datum(web_md *Wm, text_stream *k
return NULL; return NULL;
} }
@h Writing bibliographic data. @ \section{Writing bibliographic data.}
Note that a key-value pair is created if the key doesn't exist at present, Note that a key-value pair is created if the key doesn't exist at present,
so this routine never fails. so this routine never fails.
= <<*>>=
web_bibliographic_datum *Bibliographic::set_datum(web_md *Wm, text_stream *key, text_stream *val) { web_bibliographic_datum *Bibliographic::set_datum(web_md *Wm, text_stream *key, text_stream *val) {
web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key); web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key);
if (bd == NULL) @<Create a new datum, then@> if (bd == NULL) <<Create a new datum, then>>
else Str::copy(bd->value, val); else Str::copy(bd->value, val);
if (Str::eq_wide_string(key, L"Title")) @<Also set a capitalized form@>; if (Str::eq_wide_string(key, L"Title")) <<Also set a capitalized form>>;
return bd; return bd;
} }
@<Create a new datum, then@> = <<Create a new datum, then>>=
bd = CREATE(web_bibliographic_datum); bd = CREATE(web_bibliographic_datum);
bd->key = Str::duplicate(key); bd->key = Str::duplicate(key);
bd->value = Str::duplicate(val); bd->value = Str::duplicate(val);
@ -146,7 +147,7 @@ written to the "Title" key, then a full-caps "WUTHERING HEIGHTS" is
written to a "Capitalized Title" key. (This enables cover sheets which written to a "Capitalized Title" key. (This enables cover sheets which
want to typeset the title in full caps to do so.) want to typeset the title in full caps to do so.)
@<Also set a capitalized form@> = <<Also set a capitalized form>>=
TEMPORARY_TEXT(recapped) TEMPORARY_TEXT(recapped)
Str::copy(recapped, val); Str::copy(recapped, val);
LOOP_THROUGH_TEXT(P, recapped) LOOP_THROUGH_TEXT(P, recapped)

View file

@ -2,11 +2,11 @@
Manages the build metadata for an inweb project. Manages the build metadata for an inweb project.
@h About build files. @ \section{About build files.}
When we read a web, we look for a file in it called |build.txt|. If no such When we read a web, we look for a file in it called [[build.txt]]. If no such
file exists, we look for the same thing in the current working directory. file exists, we look for the same thing in the current working directory.
= <<*>>=
filename *BuildFiles::build_file_for_web(web_md *WS) { filename *BuildFiles::build_file_for_web(web_md *WS) {
filename *F = Filenames::in(WS->path_to_web, I"build.txt"); filename *F = Filenames::in(WS->path_to_web, I"build.txt");
if (TextFiles::exists(F)) return F; if (TextFiles::exists(F)) return F;
@ -17,7 +17,7 @@ filename *BuildFiles::build_file_for_web(web_md *WS) {
@ The format of such a file is very simple: up to three text fields: @ The format of such a file is very simple: up to three text fields:
= <<*>>=
typedef struct build_file_data { typedef struct build_file_data {
struct text_stream *prerelease_text; struct text_stream *prerelease_text;
struct text_stream *build_code; struct text_stream *build_code;
@ -26,7 +26,7 @@ typedef struct build_file_data {
@ Here's how to read in a build file: @ Here's how to read in a build file:
= <<*>>=
build_file_data BuildFiles::read(filename *F) { build_file_data BuildFiles::read(filename *F) {
build_file_data bfd; build_file_data bfd;
bfd.prerelease_text = Str::new(); bfd.prerelease_text = Str::new();
@ -55,7 +55,7 @@ void BuildFiles::build_file_helper(text_stream *text, text_file_position *tfp, v
@ And here is how to write one: @ And here is how to write one:
= <<*>>=
void BuildFiles::write(build_file_data bfd, filename *F) { void BuildFiles::write(build_file_data bfd, filename *F) {
text_stream vr_stream; text_stream vr_stream;
text_stream *OUT = &vr_stream; text_stream *OUT = &vr_stream;
@ -69,11 +69,11 @@ void BuildFiles::write(build_file_data bfd, filename *F) {
Streams::close(OUT); Streams::close(OUT);
} }
@h Bibliographic implications. @ \section{Bibliographic implications.}
Whenever a web is read in by Inweb, its build file is looked at in order to Whenever a web is read in by Inweb, its build file is looked at in order to
set some bibliographic data. set some bibliographic data.
= <<*>>=
void BuildFiles::set_bibliographic_data_for(web_md *WS) { void BuildFiles::set_bibliographic_data_for(web_md *WS) {
filename *F = BuildFiles::build_file_for_web(WS); filename *F = BuildFiles::build_file_for_web(WS);
if (F) { if (F) {
@ -92,10 +92,10 @@ synthesize the semantic version number for the project. Note that this is
called even if no build file had ever been found, so it's quite legal for called even if no build file had ever been found, so it's quite legal for
the Contents page to specify all of this. the Contents page to specify all of this.
If no error occurs, then the expansion |[[Semantic Version Number]]| is If no error occurs, then the expansion [[[[Semantic Version Number]]]] is
guaranteed to produce a semver-legal version number. guaranteed to produce a semver-legal version number.
= <<*>>=
void BuildFiles::deduce_semver(web_md *WS) { void BuildFiles::deduce_semver(web_md *WS) {
TEMPORARY_TEXT(combined) TEMPORARY_TEXT(combined)
text_stream *s = Bibliographic::get_datum(WS, I"Semantic Version Number"); text_stream *s = Bibliographic::get_datum(WS, I"Semantic Version Number");
@ -121,11 +121,11 @@ void BuildFiles::deduce_semver(web_md *WS) {
DISCARD_TEXT(combined) DISCARD_TEXT(combined)
} }
@h Advancing. @ \section{Advancing.}
We update the build date to today and, if supplied, also increment the build We update the build date to today and, if supplied, also increment the build
number if we find that the date has changed. number if we find that the date has changed.
= <<*>>=
void BuildFiles::advance_for_web(web_md *WS) { void BuildFiles::advance_for_web(web_md *WS) {
filename *F = BuildFiles::build_file_for_web(WS); filename *F = BuildFiles::build_file_for_web(WS);
if (F) BuildFiles::advance(F); if (F) BuildFiles::advance(F);
@ -141,10 +141,10 @@ void BuildFiles::advance(filename *F) {
} }
@ The standard date format we use is "26 February 2018". If the contents of @ The standard date format we use is "26 February 2018". If the contents of
|dateline| match today's date in this format, we return |TRUE|; otherwise we [[dateline]] match today's date in this format, we return [[TRUE]]; otherwise we
rewrite |dateline| to today and return |FALSE|. rewrite [[dateline]] to today and return [[FALSE]].
= <<*>>=
int BuildFiles::dated_today(text_stream *dateline) { int BuildFiles::dated_today(text_stream *dateline) {
char *monthname[12] = { "January", "February", "March", "April", "May", "June", char *monthname[12] = { "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" }; "July", "August", "September", "October", "November", "December" };
@ -161,16 +161,16 @@ int BuildFiles::dated_today(text_stream *dateline) {
return rv; return rv;
} }
@ Traditional Inform build codes are four-character, e.g., |3Q27|. Here, we @ Traditional Inform build codes are four-character, e.g., [[3Q27]]. Here, we
read such a code and increase it by one. The two-digit code at the back is read such a code and increase it by one. The two-digit code at the back is
incremented, but rolls around from |99| to |01|, in which case the letter is incremented, but rolls around from [[99]] to [[01]], in which case the letter is
advanced, except that |I| and |O| are skipped, and if the letter passes |Z| advanced, except that [[I]] and [[O]] are skipped, and if the letter passes [[Z]]
then it rolls back around to |A| and the initial digit is incremented. then it rolls back around to [[A]] and the initial digit is incremented.
This allows for 21384 distinct build codes, enough to use one each day for This allows for 21384 distinct build codes, enough to use one each day for
some 58 years. some 58 years.
= <<*>>=
void BuildFiles::increment(text_stream *T) { void BuildFiles::increment(text_stream *T) {
if (Str::len(T) != 4) Errors::with_text("build code malformed: %S", T); if (Str::len(T) != 4) Errors::with_text("build code malformed: %S", T);
else { else {
@ -178,8 +178,8 @@ void BuildFiles::increment(text_stream *T) {
int L = Str::get_at(T, 1); int L = Str::get_at(T, 1);
int M1 = Str::get_at(T, 2) - '0'; int M1 = Str::get_at(T, 2) - '0';
int M2 = Str::get_at(T, 3) - '0'; int M2 = Str::get_at(T, 3) - '0';
if ((N < 0) || (N > 9) || (L < 'A') || (L > 'Z') || if ((N < 0) [[| (N > 9) || (L < 'A') || (L > 'Z') |]]
(M1 < 0) || (M1 > 9) || (M2 < 0) || (M2 > 9)) { (M1 < 0) [[| (M1 > 9) || (M2 < 0) |]] (M2 > 9)) {
Errors::with_text("build code malformed: %S", T); Errors::with_text("build code malformed: %S", T);
} else { } else {
M2++; M2++;

View file

@ -13,10 +13,10 @@ a program: something much simpler would surely be sufficient. And here it is.
[1] Why might we have this? Because kits of Inter code take this form. [1] Why might we have this? Because kits of Inter code take this form.
@ The simple tangler is controlled using a parcel of settings. Note also the @ The simple tangler is controlled using a parcel of settings. Note also the
|state|, which is not used by the reader itself, but instead allows the callback [[state]], which is not used by the reader itself, but instead allows the callback
functions to have a shared state of their own. functions to have a shared state of their own.
= <<*>>=
typedef struct simple_tangle_docket { typedef struct simple_tangle_docket {
void (*raw_callback)(struct text_stream *, struct simple_tangle_docket *); void (*raw_callback)(struct text_stream *, struct simple_tangle_docket *);
void (*command_callback)(struct text_stream *, struct text_stream *, void (*command_callback)(struct text_stream *, struct text_stream *,
@ -27,7 +27,7 @@ typedef struct simple_tangle_docket {
struct pathname *web_path; struct pathname *web_path;
} simple_tangle_docket; } simple_tangle_docket;
@ = <<*>>=
simple_tangle_docket SimpleTangler::new_docket( simple_tangle_docket SimpleTangler::new_docket(
void (*A)(struct text_stream *, struct simple_tangle_docket *), void (*A)(struct text_stream *, struct simple_tangle_docket *),
void (*B)(struct text_stream *, struct text_stream *, void (*B)(struct text_stream *, struct text_stream *,
@ -49,7 +49,7 @@ simple_tangle_docket SimpleTangler::new_docket(
should open), or a section (which the tangler should find and open), or a should open), or a section (which the tangler should find and open), or a
whole web of section files (ditto): whole web of section files (ditto):
= <<*>>=
void SimpleTangler::tangle_text(simple_tangle_docket *docket, text_stream *text) { void SimpleTangler::tangle_text(simple_tangle_docket *docket, text_stream *text) {
SimpleTangler::tangle_L1(docket, text, NULL, NULL, FALSE); SimpleTangler::tangle_L1(docket, text, NULL, NULL, FALSE);
} }
@ -66,7 +66,7 @@ void SimpleTangler::tangle_web(simple_tangle_docket *docket) {
SimpleTangler::tangle_L1(docket, NULL, NULL, NULL, TRUE); SimpleTangler::tangle_L1(docket, NULL, NULL, NULL, TRUE);
} }
@ = <<*>>=
void SimpleTangler::tangle_L1(simple_tangle_docket *docket, text_stream *text, void SimpleTangler::tangle_L1(simple_tangle_docket *docket, text_stream *text,
filename *F, text_stream *leafname, int whole_web) { filename *F, text_stream *leafname, int whole_web) {
TEMPORARY_TEXT(T) TEMPORARY_TEXT(T)
@ -77,7 +77,7 @@ void SimpleTangler::tangle_L1(simple_tangle_docket *docket, text_stream *text,
@ First, dispose of the "whole web" possibility. @ First, dispose of the "whole web" possibility.
= <<*>>=
void SimpleTangler::tangle_L2(OUTPUT_STREAM, text_stream *text, filename *F, void SimpleTangler::tangle_L2(OUTPUT_STREAM, text_stream *text, filename *F,
text_stream *leafname, simple_tangle_docket *docket, int whole_web) { text_stream *leafname, simple_tangle_docket *docket, int whole_web) {
if (whole_web) { if (whole_web) {
@ -95,26 +95,26 @@ void SimpleTangler::tangle_L2(OUTPUT_STREAM, text_stream *text, filename *F,
} }
} }
@ When tangling a file, we begin in |comment| mode; when tangling other matter, @ When tangling a file, we begin in [[comment]] mode; when tangling other matter,
not so much. not so much.
= <<*>>=
void SimpleTangler::tangle_L3(OUTPUT_STREAM, text_stream *text, void SimpleTangler::tangle_L3(OUTPUT_STREAM, text_stream *text,
text_stream *leafname, simple_tangle_docket *docket, filename *F) { text_stream *leafname, simple_tangle_docket *docket, filename *F) {
int comment = FALSE; int comment = FALSE;
FILE *Input_File = NULL; FILE *Input_File = NULL;
if ((Str::len(leafname) > 0) || (F)) { if ((Str::len(leafname) > 0) || (F)) {
@<Open the file@>; <<Open the file>>;
comment = TRUE; comment = TRUE;
} }
@<Tangle the material@>; <<Tangle the material>>;
if (Input_File) fclose(Input_File); if (Input_File) fclose(Input_File);
} }
@ Note that if we are looking for an explicit section -- say, |Juggling.i6t| -- @ Note that if we are looking for an explicit section -- say, [[Juggling.i6t]] --
from a web |W|, we translate that into the path |W/Sections/Juggling.i6t|. from a web [[W]], we translate that into the path [[W/Sections/Juggling.i6t]].
@<Open the file@> = <<Open the file>>=
if (F) { if (F) {
Input_File = Filenames::fopen(F, "r"); Input_File = Filenames::fopen(F, "r");
} else if (Str::len(leafname) > 0) { } else if (Str::len(leafname) > 0) {
@ -124,7 +124,7 @@ from a web |W|, we translate that into the path |W/Sections/Juggling.i6t|.
if (Input_File == NULL) if (Input_File == NULL)
(*(docket->error_callback))("unable to open the file '%S'", leafname); (*(docket->error_callback))("unable to open the file '%S'", leafname);
@<Tangle the material@> = <<Tangle the material>>=
TEMPORARY_TEXT(command) TEMPORARY_TEXT(command)
TEMPORARY_TEXT(argument) TEMPORARY_TEXT(argument)
int skip_part = FALSE, extract = FALSE; int skip_part = FALSE, extract = FALSE;
@ -132,50 +132,51 @@ from a web |W|, we translate that into the path |W/Sections/Juggling.i6t|.
do { do {
Str::clear(command); Str::clear(command);
Str::clear(argument); Str::clear(argument);
@<Read next character@>; <<Read next character>>;
NewCharacter: if (cr == EOF) break; NewCharacter: if (cr == EOF) break;
if (((cr == '@') || (cr == '=')) && (col == 1)) { if (((cr == '@') || (cr == '=')) && (col == 1)) {
int inweb_syntax = -1; int inweb_syntax = -1;
if (cr == '=') @<Read the rest of line as an equals-heading@> if (cr == '=') <<Read the rest of line as an equals-heading>>
else @<Read the rest of line as an at-heading@>; else <<Read the rest of line as an at-heading>>;
@<Act on the heading, going in or out of comment mode as appropriate@>; <<Act on the heading, going in or out of comment mode as appropriate>>;
continue; continue;
} }
if (comment == FALSE) @<Deal with material which isn't commentary@>; if (comment == FALSE) <<Deal with material which isn't commentary>>;
} while (cr != EOF); } while (cr != EOF);
DISCARD_TEXT(command) DISCARD_TEXT(command)
DISCARD_TEXT(argument) DISCARD_TEXT(argument)
@ Our text files are encoded as ISO Latin-1, not as Unicode UTF-8, so ordinary @ Our text files are encoded as ISO Latin-1, not as Unicode UTF-8, so ordinary
|fgetc| is used, and no BOM marker is parsed. Lines are assumed to be terminated [[fgetc]] is used, and no BOM marker is parsed. Lines are assumed to be terminated
with either |0x0a| or |0x0d|. (Since blank lines are harmless, we take no with either [[0x0a]] or [[0x0d]]. (Since blank lines are harmless, we take no
trouble over |0a0d| or |0d0a| combinations.) The built-in template files, almost trouble over [[0a0d]] or [[0d0a]] combinations.) The built-in template files, almost
always the only ones used, are line terminated |0x0a| in Unix fashion. always the only ones used, are line terminated [[0x0a]] in Unix fashion.
@<Read next character@> = <<Read next character>>=
if (Input_File) cr = fgetc(Input_File); if (Input_File) cr = fgetc(Input_File);
else if (text) { else if (text) {
cr = Str::get_at(text, sfp); if (cr == 0) cr = EOF; else sfp++; cr = Str::get_at(text, sfp); if (cr == 0) cr = EOF; else sfp++;
} else cr = EOF; } else cr = EOF;
col++; if ((cr == 10) || (cr == 13)) col = 0; col++; if ((cr == 10) || (cr == 13)) col = 0;
@ Here we see the limited range of Inweb syntaxes allowed; but some |@| and |=| @ Here we see the limited range of Inweb syntaxes allowed; but some [[@]] and [[=]]
commands can be used, at least. commands can be used, at least.
@d INWEB_PARAGRAPH_SYNTAX 1 <<*>>=
@d INWEB_CODE_SYNTAX 2 #define INWEB_PARAGRAPH_SYNTAX 1
@d INWEB_DASH_SYNTAX 3 #define INWEB_CODE_SYNTAX 2
@d INWEB_PURPOSE_SYNTAX 4 #define INWEB_DASH_SYNTAX 3
@d INWEB_FIGURE_SYNTAX 5 #define INWEB_PURPOSE_SYNTAX 4
@d INWEB_EQUALS_SYNTAX 6 #define INWEB_FIGURE_SYNTAX 5
@d INWEB_EXTRACT_SYNTAX 7 #define INWEB_EQUALS_SYNTAX 6
#define INWEB_EXTRACT_SYNTAX 7
@<Read the rest of line as an at-heading@> = <<Read the rest of line as an at-heading>>=
TEMPORARY_TEXT(at_cmd) TEMPORARY_TEXT(at_cmd)
int committed = FALSE, unacceptable_character = FALSE; int committed = FALSE, unacceptable_character = FALSE;
while (TRUE) { while (TRUE) {
@<Read next character@>; <<Read next character>>;
if ((committed == FALSE) && ((cr == 10) || (cr == 13) || (cr == ' '))) { if ((committed == FALSE) && ((cr == 10) [[| (cr == 13) |]] (cr == ' '))) {
if (Str::eq_wide_string(at_cmd, L"p")) if (Str::eq_wide_string(at_cmd, L"p"))
inweb_syntax = INWEB_PARAGRAPH_SYNTAX; inweb_syntax = INWEB_PARAGRAPH_SYNTAX;
else if (Str::eq_wide_string(at_cmd, L"h")) else if (Str::eq_wide_string(at_cmd, L"h"))
@ -202,7 +203,7 @@ commands can be used, at least.
} }
if (!(((cr >= 'A') && (cr <= 'Z')) || ((cr >= 'a') && (cr <= 'z')) if (!(((cr >= 'A') && (cr <= 'Z')) || ((cr >= 'a') && (cr <= 'z'))
|| ((cr >= '0') && (cr <= '9')) || ((cr >= '0') && (cr <= '9'))
|| (cr == '-') || (cr == '>') || (cr == ':') || (cr == '_'))) [[| (cr == '-') || (cr == '>') || (cr == ':') |]] (cr == '_')))
unacceptable_character = TRUE; unacceptable_character = TRUE;
if ((cr == 10) || (cr == 13)) break; if ((cr == 10) || (cr == 13)) break;
PUT_TO(at_cmd, cr); PUT_TO(at_cmd, cr);
@ -210,10 +211,10 @@ commands can be used, at least.
Str::copy(command, at_cmd); Str::copy(command, at_cmd);
DISCARD_TEXT(at_cmd) DISCARD_TEXT(at_cmd)
@<Read the rest of line as an equals-heading@> = <<Read the rest of line as an equals-heading>>=
TEMPORARY_TEXT(equals_cmd) TEMPORARY_TEXT(equals_cmd)
while (TRUE) { while (TRUE) {
@<Read next character@>; <<Read next character>>;
if ((cr == 10) || (cr == 13)) break; if ((cr == 10) || (cr == 13)) break;
PUT_TO(equals_cmd, cr); PUT_TO(equals_cmd, cr);
} }
@ -231,14 +232,14 @@ commands can be used, at least.
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
DISCARD_TEXT(equals_cmd) DISCARD_TEXT(equals_cmd)
@<Act on the heading, going in or out of comment mode as appropriate@> = <<Act on the heading, going in or out of comment mode as appropriate>>=
switch (inweb_syntax) { switch (inweb_syntax) {
case INWEB_PARAGRAPH_SYNTAX: { case INWEB_PARAGRAPH_SYNTAX: {
TEMPORARY_TEXT(heading_name) TEMPORARY_TEXT(heading_name)
Str::copy_tail(heading_name, command, 2); Str::copy_tail(heading_name, command, 2);
int c; int c;
while (((c = Str::get_last_char(heading_name)) != 0) && while (((c = Str::get_last_char(heading_name)) != 0) &&
((c == ' ') || (c == '\t') || (c == '.'))) ((c == ' ') [[| (c == '\t') |]] (c == '.')))
Str::delete_last_character(heading_name); Str::delete_last_character(heading_name);
if (Str::len(heading_name) == 0) if (Str::len(heading_name) == 0)
(*(docket->error_callback))("Empty heading name", NULL); (*(docket->error_callback))("Empty heading name", NULL);
@ -266,11 +267,11 @@ commands can be used, at least.
case INWEB_FIGURE_SYNTAX: break; case INWEB_FIGURE_SYNTAX: break;
} }
@<Deal with material which isn't commentary@> = <<Deal with material which isn't commentary>>=
if (cr == '{') { if (cr == '{') {
@<Read next character@>; <<Read next character>>;
if ((cr == '-') && (docket->command_callback)) { if ((cr == '-') && (docket->command_callback)) {
@<Read up to the next close brace as a braced command and argument@>; <<Read up to the next close brace as a braced command and argument>>;
if (Str::get_first_char(command) == '!') continue; if (Str::get_first_char(command) == '!') continue;
(*(docket->command_callback))(OUT, command, argument, docket); (*(docket->command_callback))(OUT, command, argument, docket);
continue; continue;
@ -280,9 +281,9 @@ commands can be used, at least.
} }
} }
if ((cr == '(') && (docket->bplus_callback)) { if ((cr == '(') && (docket->bplus_callback)) {
@<Read next character@>; <<Read next character>>;
if (cr == '+') { if (cr == '+') {
@<Read up to the next plus close-bracket as an I7 expression@>; <<Read up to the next plus close-bracket as an I7 expression>>;
continue; continue;
} else { /* otherwise the open bracket was a literal */ } else { /* otherwise the open bracket was a literal */
PUT_TO(OUT, '('); PUT_TO(OUT, '(');
@ -291,29 +292,29 @@ commands can be used, at least.
} }
PUT_TO(OUT, cr); PUT_TO(OUT, cr);
@ And here we read a normal command. The command name must not include |}| @ And here we read a normal command. The command name must not include [[}]]
or |:|. If there is no |:| then the argument is left unset (so that it will or [[:]]. If there is no [[:]] then the argument is left unset (so that it will
be the empty string: see above). The argument must not include |}|. be the empty string: see above). The argument must not include [[}]].
@<Read up to the next close brace as a braced command and argument@> = <<Read up to the next close brace as a braced command and argument>>=
Str::clear(command); Str::clear(command);
Str::clear(argument); Str::clear(argument);
int com_mode = TRUE; int com_mode = TRUE;
while (TRUE) { while (TRUE) {
@<Read next character@>; <<Read next character>>;
if ((cr == '}') || (cr == EOF)) break; if ((cr == '}') || (cr == EOF)) break;
if ((cr == ':') && (com_mode)) { com_mode = FALSE; continue; } if ((cr == ':') && (com_mode)) { com_mode = FALSE; continue; }
if (com_mode) PUT_TO(command, cr); if (com_mode) PUT_TO(command, cr);
else PUT_TO(argument, cr); else PUT_TO(argument, cr);
} }
@ And similarly, for the |(+| ... |+)| notation which was once used to mark @ And similarly, for the [[(+| ... |+)]] notation which was once used to mark
I7 material within I6: I7 material within I6:
@<Read up to the next plus close-bracket as an I7 expression@> = <<Read up to the next plus close-bracket as an I7 expression>>=
TEMPORARY_TEXT(material) TEMPORARY_TEXT(material)
while (TRUE) { while (TRUE) {
@<Read next character@>; <<Read next character>>;
if (cr == EOF) break; if (cr == EOF) break;
if ((cr == ')') && (Str::get_last_char(material) == '+')) { if ((cr == ')') && (Str::get_last_char(material) == '+')) {
Str::delete_last_character(material); break; } Str::delete_last_character(material); break; }

View file

@ -2,32 +2,33 @@
To search for included modules, and track dependencies between them. To search for included modules, and track dependencies between them.
@h Creation. @ \section{Creation.}
Each web of source material discovered by Inweb is given one of the following. Each web of source material discovered by Inweb is given one of the following.
Ordinarily these are found only when reading in a web for weaving, tangling Ordinarily these are found only when reading in a web for weaving, tangling
and so on: in the vast majority of Inweb runs, all modules will have the and so on: in the vast majority of Inweb runs, all modules will have the
"module origin marker" |READING_WEB_MOM|. But when Inweb is constructing a "module origin marker" [[READING_WEB_MOM]]. But when Inweb is constructing a
makefile for a suite of tools, it can also discover multiple webs by other makefile for a suite of tools, it can also discover multiple webs by other
means. means.
@e READING_WEB_MOM from 0 <<*>>=
@e MAKEFILE_TOOL_MOM enum READING_WEB_MOM from 0
@e MAKEFILE_WEB_MOM enum MAKEFILE_TOOL_MOM
@e MAKEFILE_MODULE_MOM enum MAKEFILE_WEB_MOM
enum MAKEFILE_MODULE_MOM
= <<*>>=
typedef struct module { typedef struct module {
struct pathname *module_location; struct pathname *module_location;
struct text_stream *module_name; struct text_stream *module_name;
struct linked_list *dependencies; /* of |module|: which other modules does this need? */ struct linked_list *dependencies; /* of [[module]]: which other modules does this need? */
struct text_stream *module_tag; struct text_stream *module_tag;
int origin_marker; /* one of the |*_MOM| values above */ int origin_marker; /* one of the [[*_MOM]] values above */
struct linked_list *chapters_md; /* of |chapter_md|: just the ones in this module */ struct linked_list *chapters_md; /* of [[chapter_md]]: just the ones in this module */
struct linked_list *sections_md; /* of |section_md|: just the ones in this module */ struct linked_list *sections_md; /* of [[section_md]]: just the ones in this module */
CLASS_DEFINITION CLASS_DEFINITION
} module; } module;
@ = <<*>>=
module *WebModules::new(text_stream *name, pathname *at, int m) { module *WebModules::new(text_stream *name, pathname *at, int m) {
module *M = CREATE(module); module *M = CREATE(module);
M->module_location = at; M->module_location = at;
@ -44,48 +45,48 @@ module *WebModules::new(text_stream *name, pathname *at, int m) {
contains a suite of utility routines, or a major component of a program, but contains a suite of utility routines, or a major component of a program, but
which is not a program in its own right. which is not a program in its own right.
Internally, though, every web produces a |module| structure. The one for the Internally, though, every web produces a [[module]] structure. The one for the
main web -- which can be tangled, and results in an actual program -- is main web -- which can be tangled, and results in an actual program -- is
internally named |"(main)"|, a name which the user will never see. internally named [["(main)"]], a name which the user will never see.
= <<*>>=
module *WebModules::create_main_module(web_md *WS) { module *WebModules::create_main_module(web_md *WS) {
return WebModules::new(I"(main)", WS->path_to_web, READING_WEB_MOM); return WebModules::new(I"(main)", WS->path_to_web, READING_WEB_MOM);
} }
@h Dependencies. @ \section{Dependencies.}
When web A imports module B, we will say that A is dependent on B. A web When web A imports module B, we will say that A is dependent on B. A web
can import multiple modules, so there can a list of dependencies. These are can import multiple modules, so there can a list of dependencies. These are
needed when constructing makefiles, since the source code in B affects the needed when constructing makefiles, since the source code in B affects the
program generated by A. program generated by A.
= <<*>>=
void WebModules::dependency(module *A, module *B) { void WebModules::dependency(module *A, module *B) {
if ((A == NULL) || (B == NULL)) internal_error("no module"); if ((A == NULL) || (B == NULL)) internal_error("no module");
ADD_TO_LINKED_LIST(B, module, A->dependencies); ADD_TO_LINKED_LIST(B, module, A->dependencies);
} }
@h Searching. @ \section{Searching.}
The following abstracts the idea of a place where modules might be found. The following abstracts the idea of a place where modules might be found.
(At one time there was going to be a more elaborate search hierarchy.) (At one time there was going to be a more elaborate search hierarchy.)
= <<*>>=
typedef struct module_search { typedef struct module_search {
struct pathname *path_to_search; struct pathname *path_to_search;
CLASS_DEFINITION CLASS_DEFINITION
} module_search; } module_search;
@ = <<*>>=
module_search *WebModules::make_search_path(pathname *ext_path) { module_search *WebModules::make_search_path(pathname *ext_path) {
module_search *ms = CREATE(module_search); module_search *ms = CREATE(module_search);
ms->path_to_search = ext_path; ms->path_to_search = ext_path;
return ms; return ms;
} }
@ When a web's contents page says to |import Blah|, how do we find the module @ When a web's contents page says to [[import Blah]], how do we find the module
called |Blah| on disc? We try four possibilities in sequence: called [[Blah]] on disc? We try four possibilities in sequence:
= <<*>>=
module *WebModules::find(web_md *WS, module_search *ms, text_stream *name, pathname *X) { module *WebModules::find(web_md *WS, module_search *ms, text_stream *name, pathname *X) {
TEMPORARY_TEXT(T) TEMPORARY_TEXT(T)
WRITE_TO(T, "%S-module", name); WRITE_TO(T, "%S-module", name);
@ -97,16 +98,16 @@ module *WebModules::find(web_md *WS, module_search *ms, text_stream *name, pathn
int N = 4; int N = 4;
for (int i=0; i<N; i++) { for (int i=0; i<N; i++) {
pathname *P = Pathnames::from_text_relative(tries[i], T); pathname *P = Pathnames::from_text_relative(tries[i], T);
if ((P) && (WebModules::exists(P))) @<Accept this directory as the module@>; if ((P) && (WebModules::exists(P))) <<Accept this directory as the module>>;
} }
DISCARD_TEXT(T) DISCARD_TEXT(T)
return NULL; return NULL;
} }
@ When the module is found (if it is), a suitable module structure is made, @ When the module is found (if it is), a suitable module structure is made,
and a dependency created from the web's |(main)| module to this one. and a dependency created from the web's [[(main)]] module to this one.
@<Accept this directory as the module@> = <<Accept this directory as the module>>=
pathname *Q = Pathnames::from_text(name); pathname *Q = Pathnames::from_text(name);
module *M = WebModules::new(Pathnames::directory_name(Q), P, READING_WEB_MOM); module *M = WebModules::new(Pathnames::directory_name(Q), P, READING_WEB_MOM);
WebModules::dependency(WS->as_module, M); WebModules::dependency(WS->as_module, M);
@ -115,30 +116,30 @@ and a dependency created from the web's |(main)| module to this one.
@ We accept that a plausibly-named directory is indeed the module being @ We accept that a plausibly-named directory is indeed the module being
sought if it looks like a web. sought if it looks like a web.
= <<*>>=
int WebModules::exists(pathname *P) { int WebModules::exists(pathname *P) {
return WebMetadata::directory_looks_like_a_web(P); return WebMetadata::directory_looks_like_a_web(P);
} }
@h Resolving cross-reference names. @ \section{Resolving cross-reference names.}
Suppose we are in module |from_M| and want to understand which section of Suppose we are in module [[from_M]] and want to understand which section of
a relevant web |text| might refer to. It could be the name of a module, a relevant web [[text]] might refer to. It could be the name of a module,
either this one or one dependent on it; or the name of a chapter in one either this one or one dependent on it; or the name of a chapter in one
of those, or the shortened forms of those; or the name of a section. It of those, or the shortened forms of those; or the name of a section. It
may match multiple possibilities: we return how many, and if this is may match multiple possibilities: we return how many, and if this is
positive, we write the module in which the first find was made in |*return M|, positive, we write the module in which the first find was made in [[*return M]],
the section in |*return_Sm|, and set the flag |*named_as_module| according the section in [[*return_Sm]], and set the flag [[*named_as_module]] according
to whether the reference was a bare module name (say, "foundation") or not. to whether the reference was a bare module name (say, "foundation") or not.
Note that we consider first the possibilities within |from_M|: we only Note that we consider first the possibilities within [[from_M]]: we only
look at other modules if there are none. Thus, an unambiguous result in look at other modules if there are none. Thus, an unambiguous result in
|from_M| is good enough, even if there are other possibilities elsewhere. [[from_M]] is good enough, even if there are other possibilities elsewhere.
A reference in the form |module: reference| is taken to be in the module A reference in the form [[module: reference]] is taken to be in the module
of that name: for example, |"foundation: Web Modules"| would find the of that name: for example, [["foundation: Web Modules"]] would find the
section of code you are now reading. section of code you are now reading.
= <<*>>=
int WebModules::named_reference(module **return_M, section_md **return_Sm, int WebModules::named_reference(module **return_M, section_md **return_Sm,
int *named_as_module, text_stream *title, module *from_M, text_stream *text, int *named_as_module, text_stream *title, module *from_M, text_stream *text,
int list, int sections_only) { int list, int sections_only) {
@ -156,7 +157,7 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
} }
LOOP_OVER_LINKED_LIST(M, module, from_M->dependencies) { LOOP_OVER_LINKED_LIST(M, module, from_M->dependencies) {
if (Str::eq_insensitive(M->module_name, seek_module)) { if (Str::eq_insensitive(M->module_name, seek_module)) {
@<Look for references to chapters or sections in M@>; <<Look for references to chapters or sections in M>>;
} }
} }
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
@ -164,20 +165,20 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
for (int stage = 1; ((finds == 0) && (stage <= 2)); stage++) { for (int stage = 1; ((finds == 0) && (stage <= 2)); stage++) {
if (stage == 1) { if (stage == 1) {
M = from_M; M = from_M;
@<Look for references to chapters or sections in M@>; <<Look for references to chapters or sections in M>>;
} }
if (stage == 2) { if (stage == 2) {
LOOP_OVER_LINKED_LIST(M, module, from_M->dependencies) LOOP_OVER_LINKED_LIST(M, module, from_M->dependencies)
@<Look for references to chapters or sections in M@>; <<Look for references to chapters or sections in M>>;
} }
} }
return finds; return finds;
} }
@<Look for references to chapters or sections in M@> = <<Look for references to chapters or sections in M>>=
if (M == NULL) internal_error("no module"); if (M == NULL) internal_error("no module");
if (Str::eq_insensitive(M->module_name, seek)) if (Str::eq_insensitive(M->module_name, seek))
@<Found first section in module@>; <<Found first section in module>>;
chapter_md *Cm; chapter_md *Cm;
section_md *Sm; section_md *Sm;
LOOP_OVER_LINKED_LIST(Cm, chapter_md, M->chapters_md) { LOOP_OVER_LINKED_LIST(Cm, chapter_md, M->chapters_md) {
@ -185,13 +186,13 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
((Str::eq_insensitive(Cm->ch_title, seek)) || ((Str::eq_insensitive(Cm->ch_title, seek)) ||
(Str::eq_insensitive(Cm->ch_basic_title, seek)) || (Str::eq_insensitive(Cm->ch_basic_title, seek)) ||
(Str::eq_insensitive(Cm->ch_decorated_title, seek)))) (Str::eq_insensitive(Cm->ch_decorated_title, seek))))
@<Found first section in chapter@>; <<Found first section in chapter>>;
LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md) LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md)
if (Str::eq_insensitive(Sm->sect_title, seek)) if (Str::eq_insensitive(Sm->sect_title, seek))
@<Found section by name@>; <<Found section by name>>;
} }
@<Found first section in module@> = <<Found first section in module>>=
finds++; finds++;
if (finds == 1) { if (finds == 1) {
*return_M = M; *return_Sm = FIRST_IN_LINKED_LIST(section_md, M->sections_md); *return_M = M; *return_Sm = FIRST_IN_LINKED_LIST(section_md, M->sections_md);
@ -200,7 +201,7 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
} }
if (list) WRITE_TO(STDERR, "(%d) Module '%S'\n", finds, M->module_name); if (list) WRITE_TO(STDERR, "(%d) Module '%S'\n", finds, M->module_name);
@<Found first section in chapter@> = <<Found first section in chapter>>=
finds++; finds++;
if (finds == 1) { if (finds == 1) {
*return_M = M; *return_Sm = FIRST_IN_LINKED_LIST(section_md, Cm->sections_md); *return_M = M; *return_Sm = FIRST_IN_LINKED_LIST(section_md, Cm->sections_md);
@ -209,7 +210,7 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
if (list) WRITE_TO(STDERR, "(%d) Chapter '%S' of module '%S'\n", if (list) WRITE_TO(STDERR, "(%d) Chapter '%S' of module '%S'\n",
finds, Cm->ch_title, M->module_name); finds, Cm->ch_title, M->module_name);
@<Found section by name@> = <<Found section by name>>=
finds++; finds++;
if (finds == 1) { if (finds == 1) {
*return_M = M; *return_Sm = Sm; *return_M = M; *return_Sm = Sm;

View file

@ -3,7 +3,7 @@
To read the structure of a literate programming web from a path in the file To read the structure of a literate programming web from a path in the file
system. system.
@h Introduction. @ \section{Introduction.}
Webs are literate programs for the Inweb LP system. A single web consists of Webs are literate programs for the Inweb LP system. A single web consists of
a number of chapters (though sometimes just one, called "Sections"), each a number of chapters (though sometimes just one, called "Sections"), each
of which consists of a number of sections. A web can represent a stand-alone of which consists of a number of sections. A web can represent a stand-alone
@ -13,21 +13,22 @@ called a "module".
Inweb syntax has gradually shifted over the years, but there are two main Inweb syntax has gradually shifted over the years, but there are two main
versions: the second was cleaned up and simplified from the first in 2019. versions: the second was cleaned up and simplified from the first in 2019.
@e V1_SYNTAX from 1 <<*>>=
@e V2_SYNTAX enum V1_SYNTAX from 1
enum V2_SYNTAX
@h Web MD. @ \section{Web MD.}
No relation to the website of the same name: MD here stands for metadata. No relation to the website of the same name: MD here stands for metadata.
Our task in this section will be to read a web from the filing system and Our task in this section will be to read a web from the filing system and
produce the following metadata structure. produce the following metadata structure.
Each web produces a single instance of |web_md|: Each web produces a single instance of [[web_md]]:
= <<*>>=
typedef struct web_md { typedef struct web_md {
struct pathname *path_to_web; /* relative to the current working directory */ struct pathname *path_to_web; /* relative to the current working directory */
struct filename *single_file; /* relative to the current working directory */ struct filename *single_file; /* relative to the current working directory */
struct linked_list *bibliographic_data; /* of |web_bibliographic_datum| */ struct linked_list *bibliographic_data; /* of [[web_bibliographic_datum]] */
struct semantic_version_number version_number; /* as deduced from bibliographic data */ struct semantic_version_number version_number; /* as deduced from bibliographic data */
int default_syntax; /* which version syntax the sections will have */ int default_syntax; /* which version syntax the sections will have */
int chaptered; /* has the author explicitly divided it into named chapters? */ int chaptered; /* has the author explicitly divided it into named chapters? */
@ -35,20 +36,20 @@ typedef struct web_md {
struct module *as_module; /* the root of a small dependency graph */ struct module *as_module; /* the root of a small dependency graph */
struct filename *contents_filename; /* or |NULL| for a single-file web */ struct filename *contents_filename; /* or [[NULL]] for a single-file web */
struct linked_list *tangle_target_names; /* of |text_stream| */ struct linked_list *tangle_target_names; /* of [[text_stream]] */
struct linked_list *header_filenames; /* of |filename| */ struct linked_list *header_filenames; /* of [[filename]] */
struct linked_list *chapters_md; /* of |chapter_md| */ struct linked_list *chapters_md; /* of [[chapter_md]] */
struct linked_list *sections_md; /* of |section_md| */ struct linked_list *sections_md; /* of [[section_md]] */
CLASS_DEFINITION CLASS_DEFINITION
} web_md; } web_md;
@ The |chapters_md| list in a |web_md| contains these as its entries: @ The [[chapters_md]] list in a [[web_md]] contains these as its entries:
= <<*>>=
typedef struct chapter_md { typedef struct chapter_md {
struct text_stream *ch_range; /* e.g., |P| for Preliminaries, |7| for Chapter 7, |C| for Appendix C */ struct text_stream *ch_range; /* e.g., [[P| for Preliminaries, |7| for Chapter 7, |C]] for Appendix C */
struct text_stream *ch_title; /* e.g., "Chapter 3: Fresh Water Fish" */ struct text_stream *ch_title; /* e.g., "Chapter 3: Fresh Water Fish" */
struct text_stream *ch_basic_title; /* e.g., "Chapter 3" */ struct text_stream *ch_basic_title; /* e.g., "Chapter 3" */
struct text_stream *ch_decorated_title; /* e.g., "Fresh Water Fish" */ struct text_stream *ch_decorated_title; /* e.g., "Fresh Water Fish" */
@ -58,13 +59,13 @@ typedef struct chapter_md {
int imported; /* from a different web? */ int imported; /* from a different web? */
struct linked_list *sections_md; /* of |section_md| */ struct linked_list *sections_md; /* of [[section_md]] */
CLASS_DEFINITION CLASS_DEFINITION
} chapter_md; } chapter_md;
@ And the |sections_md| list in a |chapter_md| contains these as its entries: @ And the [[sections_md]] list in a [[chapter_md]] contains these as its entries:
= <<*>>=
typedef struct section_md { typedef struct section_md {
struct text_stream *sect_title; /* e.g., "Program Control" */ struct text_stream *sect_title; /* e.g., "Program Control" */
struct text_stream *sect_range; /* e.g., "2/ct" */ struct text_stream *sect_range; /* e.g., "2/ct" */
@ -82,13 +83,13 @@ typedef struct section_md {
CLASS_DEFINITION CLASS_DEFINITION
} section_md; } section_md;
@h Reading from the file system. @ \section{Reading from the file system.}
Webs can be stored in two ways: as a directory containing a multitude of files, Webs can be stored in two ways: as a directory containing a multitude of files,
in which case the pathname |P| is supplied; or as a single file with everything in which case the pathname [[P]] is supplied; or as a single file with everything
in one (and thus, implicitly, a single chapter and a single section), in which in one (and thus, implicitly, a single chapter and a single section), in which
case a filename |alt_F| is supplied. case a filename [[alt_F]] is supplied.
= <<*>>=
web_md *WebMetadata::get_without_modules(pathname *P, filename *alt_F) { web_md *WebMetadata::get_without_modules(pathname *P, filename *alt_F) {
return WebMetadata::get(P, alt_F, V2_SYNTAX, NULL, FALSE, FALSE, NULL); return WebMetadata::get(P, alt_F, V2_SYNTAX, NULL, FALSE, FALSE, NULL);
} }
@ -97,20 +98,20 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
module_search *I, int verbosely, int including_modules, pathname *path_to_inweb) { module_search *I, int verbosely, int including_modules, pathname *path_to_inweb) {
if ((including_modules) && (I == NULL)) I = WebModules::make_search_path(NULL); if ((including_modules) && (I == NULL)) I = WebModules::make_search_path(NULL);
web_md *Wm = CREATE(web_md); web_md *Wm = CREATE(web_md);
@<Begin the bibliographic data@>; <<Begin the bibliographic data>>;
@<Initialise the rest of the web MD@>; <<Initialise the rest of the web MD>>;
WebMetadata::read_contents_page(Wm, Wm->as_module, I, verbosely, WebMetadata::read_contents_page(Wm, Wm->as_module, I, verbosely,
including_modules, NULL, path_to_inweb); including_modules, NULL, path_to_inweb);
@<Consolidate the bibliographic data@>; <<Consolidate the bibliographic data>>;
@<Work out the section ranges@>; <<Work out the section ranges>>;
return Wm; return Wm;
} }
@<Begin the bibliographic data@> = <<Begin the bibliographic data>>=
Wm->bibliographic_data = NEW_LINKED_LIST(web_bibliographic_datum); Wm->bibliographic_data = NEW_LINKED_LIST(web_bibliographic_datum);
Bibliographic::initialise_data(Wm); Bibliographic::initialise_data(Wm);
@<Initialise the rest of the web MD@> = <<Initialise the rest of the web MD>>=
if (P) { if (P) {
Wm->path_to_web = P; Wm->path_to_web = P;
Wm->single_file = NULL; Wm->single_file = NULL;
@ -130,14 +131,14 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
Wm->header_filenames = NEW_LINKED_LIST(filename); Wm->header_filenames = NEW_LINKED_LIST(filename);
Wm->as_module = WebModules::create_main_module(Wm); Wm->as_module = WebModules::create_main_module(Wm);
@<Consolidate the bibliographic data@> = <<Consolidate the bibliographic data>>=
Bibliographic::check_required_data(Wm); Bibliographic::check_required_data(Wm);
BuildFiles::set_bibliographic_data_for(Wm); BuildFiles::set_bibliographic_data_for(Wm);
BuildFiles::deduce_semver(Wm); BuildFiles::deduce_semver(Wm);
@ If no range is supplied, we make one ourselves. @ If no range is supplied, we make one ourselves.
@<Work out the section ranges@> = <<Work out the section ranges>>=
int sequential = FALSE; /* are we numbering sections sequentially? */ int sequential = FALSE; /* are we numbering sections sequentially? */
if (Str::eq(Bibliographic::get_datum(Wm, I"Sequential Section Ranges"), I"On")) if (Str::eq(Bibliographic::get_datum(Wm, I"Sequential Section Ranges"), I"On"))
sequential = TRUE; sequential = TRUE;
@ -147,12 +148,12 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
int section_counter = 1; int section_counter = 1;
LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md) { LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md) {
if (Str::len(Sm->sect_range) == 0) if (Str::len(Sm->sect_range) == 0)
@<Concoct a range for section Sm in chapter Cm in web Wm@>; <<Concoct a range for section Sm in chapter Cm in web Wm>>;
section_counter++; section_counter++;
} }
} }
@<Concoct a range for section Sm in chapter Cm in web Wm@> = <<Concoct a range for section Sm in chapter Cm in web Wm>>=
if (sequential) { if (sequential) {
WRITE_TO(Sm->sect_range, "%S/", Cm->ch_range); WRITE_TO(Sm->sect_range, "%S/", Cm->ch_range);
WRITE_TO(Sm->sect_range, "s%d", section_counter); WRITE_TO(Sm->sect_range, "s%d", section_counter);
@ -162,17 +163,17 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
do { do {
Str::clear(Sm->sect_range); Str::clear(Sm->sect_range);
WRITE_TO(Sm->sect_range, "%S/", Cm->ch_range); WRITE_TO(Sm->sect_range, "%S/", Cm->ch_range);
@<Make the tail using this many consonants from each word@>; <<Make the tail using this many consonants from each word>>;
if (--letters_from_each_word == 0) break; if (--letters_from_each_word == 0) break;
} while (Str::len(Sm->sect_range) > 5); } while (Str::len(Sm->sect_range) > 5);
@<Terminate with disambiguating numbers in case of collisions@>; <<Terminate with disambiguating numbers in case of collisions>>;
} }
@ We collapse words to an initial letter plus consonants: thus "electricity" @ We collapse words to an initial letter plus consonants: thus "electricity"
would be "elctrcty", since we don't count "y" as a vowel here. would be "elctrcty", since we don't count "y" as a vowel here.
@<Make the tail using this many consonants from each word@> = <<Make the tail using this many consonants from each word>>=
int sn = 0, sw = Str::len(Sm->sect_range); int sn = 0, sw = Str::len(Sm->sect_range);
if (Platform::is_folder_separator(Str::get_at(from, sn))) sn++; if (Platform::is_folder_separator(Str::get_at(from, sn))) sn++;
int letters_from_current_word = 0; int letters_from_current_word = 0;
@ -197,7 +198,7 @@ would be "elctrcty", since we don't count "y" as a vowel here.
@ We never want two sections to have the same range. @ We never want two sections to have the same range.
@<Terminate with disambiguating numbers in case of collisions@> = <<Terminate with disambiguating numbers in case of collisions>>=
TEMPORARY_TEXT(original_range) TEMPORARY_TEXT(original_range)
Str::copy(original_range, Sm->sect_range); Str::copy(original_range, Sm->sect_range);
int disnum = 0, collision = FALSE; int disnum = 0, collision = FALSE;
@ -224,10 +225,10 @@ would be "elctrcty", since we don't count "y" as a vowel here.
} while (collision); } while (collision);
DISCARD_TEXT(original_range) DISCARD_TEXT(original_range)
@h Reading the contents page. @ \section{Reading the contents page.}
Making the web begins by reading the contents section, which really isn't a Making the web begins by reading the contents section, which really isn't a
section at all (and perhaps we shouldn't pretend that it is by the use of the section at all (and perhaps we shouldn't pretend that it is by the use of the
|.w| file extension, but we probably want it to have the same file extension, [[.w]] file extension, but we probably want it to have the same file extension,
and its syntax is chosen so that syntax-colouring for regular sections doesn't and its syntax is chosen so that syntax-colouring for regular sections doesn't
make it look odd). When the word "section" is used in the Inweb code, it make it look odd). When the word "section" is used in the Inweb code, it
almost always means "section other than the contents". almost always means "section other than the contents".
@ -242,7 +243,7 @@ With a single-file web, the "contents section" doesn't exist as a file in its
own right: instead, it's the top few lines of the single file. We handle that own right: instead, it's the top few lines of the single file. We handle that
by halting at the junction point. by halting at the junction point.
= <<*>>=
typedef struct reader_state { typedef struct reader_state {
struct web_md *Wm; struct web_md *Wm;
struct filename *contents_filename; struct filename *contents_filename;
@ -268,7 +269,7 @@ void WebMetadata::read_contents_page(web_md *Wm, module *of_module,
module_search *import_path, int verbosely, module_search *import_path, int verbosely,
int including_modules, pathname *path, pathname *X) { int including_modules, pathname *path, pathname *X) {
reader_state RS; reader_state RS;
@<Initialise the reader state@>; <<Initialise the reader state>>;
int cl = TextFiles::read(RS.contents_filename, FALSE, "can't open contents file", int cl = TextFiles::read(RS.contents_filename, FALSE, "can't open contents file",
TRUE, WebMetadata::read_contents_line, NULL, &RS); TRUE, WebMetadata::read_contents_line, NULL, &RS);
@ -282,7 +283,7 @@ void WebMetadata::read_contents_page(web_md *Wm, module *of_module,
if (RS.section_count == 1) RS.last_section->is_a_singleton = TRUE; if (RS.section_count == 1) RS.last_section->is_a_singleton = TRUE;
} }
@<Initialise the reader state@> = <<Initialise the reader state>>=
RS.Wm = Wm; RS.Wm = Wm;
RS.reading_from = of_module; RS.reading_from = of_module;
RS.in_biblio = TRUE; RS.in_biblio = TRUE;
@ -318,7 +319,7 @@ void WebMetadata::read_contents_page(web_md *Wm, module *of_module,
and sets out bibliographic information about the web, the sections and their and sets out bibliographic information about the web, the sections and their
organisation, and so on. organisation, and so on.
= <<*>>=
void WebMetadata::read_contents_line(text_stream *line, text_file_position *tfp, void *X) { void WebMetadata::read_contents_line(text_stream *line, text_file_position *tfp, void *X) {
reader_state *RS = (reader_state *) X; reader_state *RS = (reader_state *) X;
if (RS->halted) return; if (RS->halted) return;
@ -328,40 +329,40 @@ void WebMetadata::read_contents_line(text_stream *line, text_file_position *tfp,
begins_with_white_space = TRUE; begins_with_white_space = TRUE;
Str::trim_white_space(line); Str::trim_white_space(line);
@<Act immediately if the web syntax version is changed@>; <<Act immediately if the web syntax version is changed>>;
int syntax = RS->Wm->default_syntax; int syntax = RS->Wm->default_syntax;
filename *filename_of_single_file_web = NULL; filename *filename_of_single_file_web = NULL;
if ((RS->halt_at_at) && (Str::get_at(line, 0) == '@')) if ((RS->halt_at_at) && (Str::get_at(line, 0) == '@'))
@<Halt at this point in the single file, and make the rest of it a one-chapter section@>; <<Halt at this point in the single file, and make the rest of it a one-chapter section>>;
@<Read regular contents material@>; <<Read regular contents material>>;
} }
@ Since the web syntax version affects how the rest of the file is read, it's @ Since the web syntax version affects how the rest of the file is read, it's
no good simply to store this up for later: we have to change the web structure no good simply to store this up for later: we have to change the web structure
immediately. immediately.
@<Act immediately if the web syntax version is changed@> = <<Act immediately if the web syntax version is changed>>=
if (Str::eq(line, I"Web Syntax Version: 1")) if (Str::eq(line, I"Web Syntax Version: 1"))
RS->Wm->default_syntax = V1_SYNTAX; RS->Wm->default_syntax = V1_SYNTAX;
else if (Str::eq(line, I"Web Syntax Version: 2")) else if (Str::eq(line, I"Web Syntax Version: 2"))
RS->Wm->default_syntax = V2_SYNTAX; RS->Wm->default_syntax = V2_SYNTAX;
@ Suppose we're reading a single-file web, and we hit the first |@| marker. @ Suppose we're reading a single-file web, and we hit the first [[@]] marker.
The contents part has now ended, so we should halt scanning. But we also need The contents part has now ended, so we should halt scanning. But we also need
to give the web a single chapter ("Sections", range "S"), which contains a to give the web a single chapter ("Sections", range "S"), which contains a
single section ("All") consisting of the remainder of the single file. single section ("All") consisting of the remainder of the single file.
@<Halt at this point in the single file, and make the rest of it a one-chapter section@> = <<Halt at this point in the single file, and make the rest of it a one-chapter section>>=
RS->halted = TRUE; RS->halted = TRUE;
text_stream *new_chapter_range = I"S"; text_stream *new_chapter_range = I"S";
text_stream *language_name = NULL; text_stream *language_name = NULL;
line = I"Sections"; line = I"Sections";
@<Create the new chapter with these details@>; <<Create the new chapter with these details>>;
line = I"All"; line = I"All";
filename_of_single_file_web = tfp->text_file_filename; filename_of_single_file_web = tfp->text_file_filename;
@<Read about, and read in, a new section@>; <<Read about, and read in, a new section>>;
return; return;
@ With those two complications out of the way, we now know that we're reading @ With those two complications out of the way, we now know that we're reading
@ -369,21 +370,21 @@ a line of contents material. At the start of the contents, this will be a
series of bibliographic data values; then there's a blank line, and then series of bibliographic data values; then there's a blank line, and then
we're into the section listing. we're into the section listing.
@<Read regular contents material@> = <<Read regular contents material>>=
if (Str::len(line) == 0) @<End bibliographic data here, at the blank line@> if (Str::len(line) == 0) <<End bibliographic data here, at the blank line>>
else if (RS->in_biblio) @<Read the bibliographic data block at the top@> else if (RS->in_biblio) <<Read the bibliographic data block at the top>>
else @<Read the roster of sections at the bottom@>; else <<Read the roster of sections at the bottom>>;
@ At this point we've gone through the bibliographic lines at the top of the @ At this point we've gone through the bibliographic lines at the top of the
contents page, and are soon going to read in the sections. contents page, and are soon going to read in the sections.
@<End bibliographic data here, at the blank line@> = <<End bibliographic data here, at the blank line>>=
RS->in_biblio = FALSE; RS->in_biblio = FALSE;
@ The bibliographic data gives lines in any order specifying values of @ The bibliographic data gives lines in any order specifying values of
variables with fixed names; a blank line ends the block. variables with fixed names; a blank line ends the block.
@<Read the bibliographic data block at the top@> = <<Read the bibliographic data block at the top>>=
if (RS->main_web_not_module) { if (RS->main_web_not_module) {
match_results mr = Regexp::create_mr(); match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, line, L"(%c+?): (%c+?) *")) { if (Regexp::match(&mr, line, L"(%c+?): (%c+?) *")) {
@ -391,7 +392,7 @@ variables with fixed names; a blank line ends the block.
Str::copy(key, mr.exp[0]); Str::copy(key, mr.exp[0]);
TEMPORARY_TEXT(value) TEMPORARY_TEXT(value)
Str::copy(value, mr.exp[1]); Str::copy(value, mr.exp[1]);
@<Set bibliographic key-value pair@>; <<Set bibliographic key-value pair>>;
DISCARD_TEXT(key) DISCARD_TEXT(key)
DISCARD_TEXT(value) DISCARD_TEXT(value)
} else { } else {
@ -403,7 +404,7 @@ variables with fixed names; a blank line ends the block.
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
} }
@<Set bibliographic key-value pair@> = <<Set bibliographic key-value pair>>=
if (Bibliographic::datum_can_be_declared(RS->Wm, key)) { if (Bibliographic::datum_can_be_declared(RS->Wm, key)) {
if (Bibliographic::datum_on_or_off(RS->Wm, key)) { if (Bibliographic::datum_on_or_off(RS->Wm, key)) {
if ((Str::ne_wide_string(value, L"On")) && (Str::ne_wide_string(value, L"Off"))) { if ((Str::ne_wide_string(value, L"On")) && (Str::ne_wide_string(value, L"Off"))) {
@ -426,20 +427,20 @@ variables with fixed names; a blank line ends the block.
@ In the bulk of the contents, we find indented lines for sections and @ In the bulk of the contents, we find indented lines for sections and
unindented ones for chapters. unindented ones for chapters.
@<Read the roster of sections at the bottom@> = <<Read the roster of sections at the bottom>>=
if (begins_with_white_space == FALSE) { if (begins_with_white_space == FALSE) {
if (Str::get_first_char(line) == '"') { if (Str::get_first_char(line) == '"') {
RS->in_purpose = TRUE; Str::delete_first_character(line); RS->in_purpose = TRUE; Str::delete_first_character(line);
} }
if (RS->in_purpose == TRUE) @<Record the purpose of the current chapter@> if (RS->in_purpose == TRUE) <<Record the purpose of the current chapter>>
else @<Read about a new chapter@>; else <<Read about a new chapter>>;
} else @<Read about, and read in, a new section@>; } else <<Read about, and read in, a new section>>;
@ After a declared chapter heading, subsequent lines form its purpose, until @ After a declared chapter heading, subsequent lines form its purpose, until
we reach a closed quote: we then stop, but remove the quotation marks. Because we reach a closed quote: we then stop, but remove the quotation marks. Because
we like a spoonful of syntactic sugar on our porridge, that's why. we like a spoonful of syntactic sugar on our porridge, that's why.
@<Record the purpose of the current chapter@> = <<Record the purpose of the current chapter>>=
if ((Str::len(line) > 0) && (Str::get_last_char(line) == '"')) { if ((Str::len(line) > 0) && (Str::get_last_char(line) == '"')) {
Str::truncate(line, Str::len(line)-1); RS->in_purpose = FALSE; Str::truncate(line, Str::len(line)-1); RS->in_purpose = FALSE;
} }
@ -451,7 +452,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
@ The title tells us everything we need to know about a chapter: @ The title tells us everything we need to know about a chapter:
@<Read about a new chapter@> = <<Read about a new chapter>>=
TEMPORARY_TEXT(new_chapter_range) /* e.g., S, P, 1, 2, 3, A, B, ... */ TEMPORARY_TEXT(new_chapter_range) /* e.g., S, P, 1, 2, 3, A, B, ... */
TEMPORARY_TEXT(pdf_leafname) TEMPORARY_TEXT(pdf_leafname)
text_stream *language_name = NULL; text_stream *language_name = NULL;
@ -460,7 +461,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
if (Regexp::match(&mr, line, L"(%c*%C) %(Independent(%c*)%)")) { if (Regexp::match(&mr, line, L"(%c*%C) %(Independent(%c*)%)")) {
text_stream *title_alone = mr.exp[0]; text_stream *title_alone = mr.exp[0];
language_name = mr.exp[1]; language_name = mr.exp[1];
@<Mark this chapter as an independent tangle target@>; <<Mark this chapter as an independent tangle target>>;
Str::copy(line, title_alone); Str::copy(line, title_alone);
} }
int this_is_a_chapter = TRUE; int this_is_a_chapter = TRUE;
@ -540,7 +541,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
DISCARD_TEXT(err) DISCARD_TEXT(err)
} }
if (this_is_a_chapter) @<Create the new chapter with these details@>; if (this_is_a_chapter) <<Create the new chapter with these details>>;
DISCARD_TEXT(new_chapter_range) DISCARD_TEXT(new_chapter_range)
DISCARD_TEXT(pdf_leafname) DISCARD_TEXT(pdf_leafname)
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
@ -548,7 +549,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
@ A chapter whose title marks it as Independent becomes a new tangle target, @ A chapter whose title marks it as Independent becomes a new tangle target,
with the same language as the main web unless stated otherwise. with the same language as the main web unless stated otherwise.
@<Mark this chapter as an independent tangle target@> = <<Mark this chapter as an independent tangle target>>=
match_results mr = Regexp::create_mr(); match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, language_name, L" *")) if (Regexp::match(&mr, language_name, L" *"))
language_name = Bibliographic::get_datum(RS->Wm, I"Language"); language_name = Bibliographic::get_datum(RS->Wm, I"Language");
@ -556,7 +557,7 @@ with the same language as the main web unless stated otherwise.
language_name = mr.exp[0]; language_name = mr.exp[0];
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
@<Create the new chapter with these details@> = <<Create the new chapter with these details>>=
chapter_md *Cm = CREATE(chapter_md); chapter_md *Cm = CREATE(chapter_md);
Cm->ch_range = Str::duplicate(new_chapter_range); Cm->ch_range = Str::duplicate(new_chapter_range);
if (line == NULL) PRINT("Nullity!\n"); if (line == NULL) PRINT("Nullity!\n");
@ -584,16 +585,16 @@ with the same language as the main web unless stated otherwise.
of registering a new section within a chapter -- more interesting because of registering a new section within a chapter -- more interesting because
we also read in and process its file. we also read in and process its file.
@<Read about, and read in, a new section@> = <<Read about, and read in, a new section>>=
section_md *Sm = CREATE(section_md); section_md *Sm = CREATE(section_md);
@<Initialise the section structure@>; <<Initialise the section structure>>;
@<Add the section to the web and the current chapter@>; <<Add the section to the web and the current chapter>>;
@<Work out the language and tangle target for the section@>; <<Work out the language and tangle target for the section>>;
if (Sm->source_file_for_section == NULL) if (Sm->source_file_for_section == NULL)
@<Work out the filename of this section file@>; <<Work out the filename of this section file>>;
@<Initialise the section structure@> = <<Initialise the section structure>>=
Sm->source_file_for_section = filename_of_single_file_web; Sm->source_file_for_section = filename_of_single_file_web;
Sm->using_syntax = syntax; Sm->using_syntax = syntax;
Sm->is_a_singleton = FALSE; Sm->is_a_singleton = FALSE;
@ -612,7 +613,7 @@ we also read in and process its file.
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
Sm->owning_module = RS->reading_from; Sm->owning_module = RS->reading_from;
@<Add the section to the web and the current chapter@> = <<Add the section to the web and the current chapter>>=
chapter_md *Cm = RS->chapter_being_scanned; chapter_md *Cm = RS->chapter_being_scanned;
RS->section_count++; RS->section_count++;
RS->last_section = Sm; RS->last_section = Sm;
@ -620,28 +621,28 @@ we also read in and process its file.
ADD_TO_LINKED_LIST(Sm, section_md, RS->Wm->sections_md); ADD_TO_LINKED_LIST(Sm, section_md, RS->Wm->sections_md);
ADD_TO_LINKED_LIST(Sm, section_md, RS->reading_from->sections_md); ADD_TO_LINKED_LIST(Sm, section_md, RS->reading_from->sections_md);
@<Work out the language and tangle target for the section@> = <<Work out the language and tangle target for the section>>=
Sm->sect_language_name = RS->chapter_being_scanned->ch_language_name; /* by default */ Sm->sect_language_name = RS->chapter_being_scanned->ch_language_name; /* by default */
match_results mr = Regexp::create_mr(); match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, line, L"(%c*%C) %(Independent (%c*) *%)")) { if (Regexp::match(&mr, line, L"(%c*%C) %(Independent (%c*) *%)")) {
text_stream *title_alone = mr.exp[0]; text_stream *title_alone = mr.exp[0];
text_stream *language_name = mr.exp[1]; text_stream *language_name = mr.exp[1];
@<Mark this section as an independent tangle target@>; <<Mark this section as an independent tangle target>>;
Str::copy(Sm->sect_title, title_alone); Str::copy(Sm->sect_title, title_alone);
} }
Regexp::dispose_of(&mr); Regexp::dispose_of(&mr);
@<Mark this section as an independent tangle target@> = <<Mark this section as an independent tangle target>>=
text_stream *p = language_name; text_stream *p = language_name;
if (Str::len(p) == 0) p = Bibliographic::get_datum(RS->Wm, I"Language"); if (Str::len(p) == 0) p = Bibliographic::get_datum(RS->Wm, I"Language");
Sm->sect_independent_language = Str::duplicate(p); Sm->sect_independent_language = Str::duplicate(p);
@ If we're told that a section is called "Bells and Whistles", what filename @ If we're told that a section is called "Bells and Whistles", what filename
is it stored in? Firstly, the leafname is normally |Bells and Whistles.w|, is it stored in? Firstly, the leafname is normally [[Bells and Whistles.w]],
but the extension used doesn't have to be |.w|: for Inform 6 template files, but the extension used doesn't have to be [[.w]]: for Inform 6 template files,
the extension needs to be |.i6t|. We allow either. the extension needs to be [[.i6t]]. We allow either.
@<Work out the filename of this section file@> = <<Work out the filename of this section file>>=
TEMPORARY_TEXT(leafname_to_use) TEMPORARY_TEXT(leafname_to_use)
WRITE_TO(leafname_to_use, "%S.i6t", Sm->sect_title); WRITE_TO(leafname_to_use, "%S.i6t", Sm->sect_title);
pathname *P = RS->path_to; pathname *P = RS->path_to;
@ -656,9 +657,9 @@ the extension needs to be |.i6t|. We allow either.
} }
DISCARD_TEXT(leafname_to_use) DISCARD_TEXT(leafname_to_use)
@h Relative pathnames or filenames. @ \section{Relative pathnames or filenames.}
= <<*>>=
int WebMetadata::directory_looks_like_a_web(pathname *P) { int WebMetadata::directory_looks_like_a_web(pathname *P) {
return TextFiles::exists(WebMetadata::contents_filename(P)); return TextFiles::exists(WebMetadata::contents_filename(P));
} }
@ -667,9 +668,9 @@ filename *WebMetadata::contents_filename(pathname *P) {
return Filenames::in(P, I"Contents.w"); return Filenames::in(P, I"Contents.w");
} }
@h Statistics. @ \section{Statistics.}
= <<*>>=
int WebMetadata::chapter_count(web_md *Wm) { int WebMetadata::chapter_count(web_md *Wm) {
int n = 0; int n = 0;
chapter_md *Cm; chapter_md *Cm;