Chapter 6: Nowebify.
This commit is contained in:
parent
a733b321a5
commit
d167c16357
5 changed files with 165 additions and 164 deletions
|
@ -2,7 +2,7 @@
|
|||
|
||||
Cross-referencing multiple webs gathered together.
|
||||
|
||||
@h Colonies of webs.
|
||||
@ \section{Colonies of webs.}
|
||||
Social spiders are said to form "colonies" when their webs are shared,[1] and
|
||||
in that spirit, a colony to Inweb is a collection of coexisting webs -- which
|
||||
share no code, and in that sense have no connection at run-time, but which
|
||||
|
@ -17,49 +17,50 @@ Orb-Weaving Spiders With Communal Webbing in a Man-Made Structural Habitat
|
|||
|
||||
@ So, then, a colony is really just a membership list:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct colony {
|
||||
struct linked_list *members; /* of |colony_member| */
|
||||
struct linked_list *members; /* of [[colony_member]] */
|
||||
struct text_stream *home; /* path of home repository */
|
||||
struct pathname *assets_path; /* where assets shared between weaves live */
|
||||
struct pathname *patterns_path; /* where additional patterns live */
|
||||
CLASS_DEFINITION
|
||||
} colony;
|
||||
|
||||
@ Each member is represented by an instance of the following. Note the |loaded|
|
||||
@ Each member is represented by an instance of the following. Note the [[loaded]]
|
||||
field: this holds metadata on the web/module in question. (Recall that a module
|
||||
is really just a web that doesn't tangle to an independent program but to a
|
||||
library of code: for almost all purposes, it's a web.) But for efficiency's
|
||||
sake, we read this metadata only on demand.
|
||||
|
||||
Note that the |path| might be either the name of a single-file web, or of a
|
||||
Note that the [[path]] might be either the name of a single-file web, or of a
|
||||
directory holding a multi-section web.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct colony_member {
|
||||
int web_rather_than_module; /* |TRUE| for a web, |FALSE| for a module */
|
||||
struct text_stream *name; /* the |N| in |N at P in W| */
|
||||
struct text_stream *path; /* the |P| in |N at P in W| */
|
||||
struct pathname *weave_path; /* the |W| in |N at P in W| */
|
||||
struct text_stream *home_leaf; /* usually |index.html|, but not for single-file webs */
|
||||
int web_rather_than_module; /* [[TRUE| for a web, |FALSE]] for a module */
|
||||
struct text_stream *name; /* the [[N| in |N at P in W]] */
|
||||
struct text_stream *path; /* the [[P| in |N at P in W]] */
|
||||
struct pathname *weave_path; /* the [[W| in |N at P in W]] */
|
||||
struct text_stream *home_leaf; /* usually [[index.html]], but not for single-file webs */
|
||||
struct text_stream *default_weave_pattern; /* for use when weaving */
|
||||
|
||||
struct web_md *loaded; /* metadata on its sections, lazily evaluated */
|
||||
struct filename *navigation; /* navigation sidebar HTML */
|
||||
struct linked_list *breadcrumb_tail; /* of |breadcrumb_request| */
|
||||
struct linked_list *breadcrumb_tail; /* of [[breadcrumb_request]] */
|
||||
CLASS_DEFINITION
|
||||
} colony_member;
|
||||
|
||||
@ And the following reads a colony file |F| and produces a suitable |colony|
|
||||
@ And the following reads a colony file [[F]] and produces a suitable [[colony]]
|
||||
object from it. This, for example, is the colony file for the Inweb repository
|
||||
at GitHub:
|
||||
= (text from Figures/colony.txt)
|
||||
|
||||
=
|
||||
(text from Figures/colony.txt)
|
||||
|
||||
<<*>>=
|
||||
typedef struct colony_reader_state {
|
||||
struct colony *province;
|
||||
struct filename *nav;
|
||||
struct linked_list *crumbs; /* of |breadcrumb_request| */
|
||||
struct linked_list *crumbs; /* of [[breadcrumb_request]] */
|
||||
struct text_stream *pattern;
|
||||
} colony_reader_state;
|
||||
|
||||
|
@ -80,14 +81,14 @@ void Colonies::load(filename *F) {
|
|||
|
||||
@ Lines from the colony file are fed, one by one, into:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Colonies::read_line(text_stream *line, text_file_position *tfp, void *v_crs) {
|
||||
colony_reader_state *crs = (colony_reader_state *) v_crs;
|
||||
colony *C = crs->province;
|
||||
|
||||
Str::trim_white_space(line); /* ignore trailing space */
|
||||
if (Str::len(line) == 0) return; /* ignore blank lines */
|
||||
if (Str::get_first_char(line) == '#') return; /* lines opening with |#| are comments */
|
||||
if (Str::get_first_char(line) == '#') return; /* lines opening with [[#]] are comments */
|
||||
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Regexp::match(&mr, line, L"(%c*?): \"*(%C+)\" at \"(%c*)\" in \"(%c*)\"")) {
|
||||
|
@ -147,7 +148,7 @@ void Colonies::read_line(text_stream *line, text_file_position *tfp, void *v_crs
|
|||
@ "Breadcrumbs" are the chain of links in a horizontal list at the top of
|
||||
the page, and this requests one.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Colonies::add_crumb(linked_list *L, text_stream *spec, text_file_position *tfp) {
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Regexp::match(&mr, spec, L"\"(%c*?)\"") == FALSE) {
|
||||
|
@ -207,11 +208,11 @@ void Colonies::write_breadcrumb(OUTPUT_STREAM, text_stream *text, text_stream *l
|
|||
}
|
||||
}
|
||||
|
||||
@h Searching.
|
||||
Given a name |T|, we try to find a colony member of that name, returning the
|
||||
@ \section{Searching.}
|
||||
Given a name [[T]], we try to find a colony member of that name, returning the
|
||||
first we find.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
colony_member *Colonies::find(text_stream *T) {
|
||||
colony *C;
|
||||
LOOP_OVER(C, colony) {
|
||||
|
@ -229,20 +230,20 @@ already in Inweb's memory (because it is the web being woven, or is a module
|
|||
imported by that web even if not now being woven). If it is, we want to use
|
||||
the data we already have; but if not, we read it in.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
module *Colonies::as_module(colony_member *CM, source_line *L, web_md *Wm) {
|
||||
if (CM->loaded == NULL) @<Perhaps the web being woven@>;
|
||||
if (CM->loaded == NULL) @<Perhaps a module imported by the web being woven@>;
|
||||
if (CM->loaded == NULL) @<Perhaps a module not yet seen@>;
|
||||
if (CM->loaded == NULL) @<Failing that, throw an error@>;
|
||||
if (CM->loaded == NULL) <<Perhaps the web being woven>>;
|
||||
if (CM->loaded == NULL) <<Perhaps a module imported by the web being woven>>;
|
||||
if (CM->loaded == NULL) <<Perhaps a module not yet seen>>;
|
||||
if (CM->loaded == NULL) <<Failing that, throw an error>>;
|
||||
return CM->loaded->as_module;
|
||||
}
|
||||
|
||||
@<Perhaps the web being woven@> =
|
||||
<<Perhaps the web being woven>>=
|
||||
if ((Wm) && (Str::eq_insensitive(Wm->as_module->module_name, CM->name)))
|
||||
CM->loaded = Wm;
|
||||
|
||||
@<Perhaps a module imported by the web being woven@> =
|
||||
<<Perhaps a module imported by the web being woven>>=
|
||||
if (Wm) {
|
||||
module *M;
|
||||
LOOP_OVER_LINKED_LIST(M, module, Wm->as_module->dependencies)
|
||||
|
@ -250,7 +251,7 @@ module *Colonies::as_module(colony_member *CM, source_line *L, web_md *Wm) {
|
|||
CM->loaded = Wm;
|
||||
}
|
||||
|
||||
@<Perhaps a module not yet seen@> =
|
||||
<<Perhaps a module not yet seen>>=
|
||||
filename *F = NULL;
|
||||
pathname *P = NULL;
|
||||
if (Str::suffix_eq(CM->path, I".inweb", 6))
|
||||
|
@ -259,14 +260,14 @@ module *Colonies::as_module(colony_member *CM, source_line *L, web_md *Wm) {
|
|||
P = Pathnames::from_text(CM->path);
|
||||
CM->loaded = WebMetadata::get_without_modules(P, F);
|
||||
|
||||
@<Failing that, throw an error@> =
|
||||
<<Failing that, throw an error>>=
|
||||
TEMPORARY_TEXT(err)
|
||||
WRITE_TO(err, "unable to load '%S'", CM->name);
|
||||
Main::error_in_web(err, L);
|
||||
|
||||
@ Finally:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
text_stream *Colonies::home(void) {
|
||||
colony *C;
|
||||
LOOP_OVER(C, colony)
|
||||
|
@ -288,9 +289,9 @@ pathname *Colonies::patterns_path(void) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
@h Cross-references.
|
||||
@ \section{Cross-references.}
|
||||
The following must decide what references like the following should refer to:
|
||||
= (text)
|
||||
|
||||
Chapter 3
|
||||
Manual
|
||||
Enumerated Constants
|
||||
|
@ -298,15 +299,15 @@ The following must decide what references like the following should refer to:
|
|||
weave_order
|
||||
foundation: Text Streams
|
||||
goldbach
|
||||
=
|
||||
The reference text is in |text|; we return |TRUE| if we can make unambiguous
|
||||
sense of it, or throw an error and return |FALSE| if not. If all is well, we
|
||||
|
||||
The reference text is in [[text]]; we return [[TRUE]] if we can make unambiguous
|
||||
sense of it, or throw an error and return [[FALSE]] if not. If all is well, we
|
||||
must write a title and URL for the link.
|
||||
|
||||
The web metadata |Wm| is for the web currently being woven, and the line |L|
|
||||
The web metadata [[Wm]] is for the web currently being woven, and the line [[L]]
|
||||
is where the reference is made from.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Colonies::resolve_reference_in_weave(text_stream *url, text_stream *title,
|
||||
filename *for_HTML_file, text_stream *text, web_md *Wm, source_line *L, int *ext) {
|
||||
int r = 0;
|
||||
|
@ -331,9 +332,9 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
colony_member *search_CM = NULL;
|
||||
int external = FALSE;
|
||||
|
||||
@<Is it an explicit URL?@>;
|
||||
@<Is it the name of a member of our colony?@>;
|
||||
@<If it contains a colon, does this indicate a section in a colony member?@>;
|
||||
<<Is it an explicit URL?>>;
|
||||
<<Is it the name of a member of our colony?>>;
|
||||
<<If it contains a colon, does this indicate a section in a colony member?>>;
|
||||
|
||||
module *found_M = NULL;
|
||||
section_md *found_Sm = NULL;
|
||||
|
@ -355,8 +356,8 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
|
||||
if (N == 0) {
|
||||
if ((L) && (external == FALSE)) {
|
||||
@<Is it the name of a function in the current web?@>;
|
||||
@<Is it the name of a type in the current web?@>;
|
||||
<<Is it the name of a function in the current web?>>;
|
||||
<<Is it the name of a type in the current web?>>;
|
||||
}
|
||||
TEMPORARY_TEXT(err)
|
||||
WRITE_TO(err, "Can't find the cross-reference '%S'", text);
|
||||
|
@ -370,11 +371,11 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
title, search_M, text, TRUE, FALSE);
|
||||
return FALSE;
|
||||
}
|
||||
@<It refers unambiguously to a single section@>;
|
||||
<<It refers unambiguously to a single section>>;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@<Is it an explicit URL?@> =
|
||||
<<Is it an explicit URL?>>=
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Regexp::match(&mr, text, L"https*://%c*")) {
|
||||
WRITE_TO(url, "%S", text);
|
||||
|
@ -385,17 +386,17 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
}
|
||||
Regexp::dispose_of(&mr);
|
||||
|
||||
@<Is it the name of a member of our colony?@> =
|
||||
<<Is it the name of a member of our colony?>>=
|
||||
search_CM = Colonies::find(text);
|
||||
if (search_CM) {
|
||||
module *found_M = Colonies::as_module(search_CM, L, Wm);
|
||||
section_md *found_Sm = FIRST_IN_LINKED_LIST(section_md, found_M->sections_md);
|
||||
int bare_module_name = TRUE;
|
||||
WRITE_TO(title, "%S", search_CM->name);
|
||||
@<It refers unambiguously to a single section@>;
|
||||
<<It refers unambiguously to a single section>>;
|
||||
}
|
||||
|
||||
@<If it contains a colon, does this indicate a section in a colony member?@> =
|
||||
<<If it contains a colon, does this indicate a section in a colony member?>>=
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Regexp::match(&mr, text, L"(%c*?): (%c*)")) {
|
||||
search_CM = Colonies::find(mr.exp[0]);
|
||||
|
@ -411,7 +412,7 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
}
|
||||
Regexp::dispose_of(&mr);
|
||||
|
||||
@<Is it the name of a function in the current web?@> =
|
||||
<<Is it the name of a function in the current web?>>=
|
||||
language_function *fn;
|
||||
LOOP_OVER(fn, language_function) {
|
||||
if (Str::eq_insensitive(fn->function_name, text)) {
|
||||
|
@ -422,7 +423,7 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
}
|
||||
}
|
||||
|
||||
@<Is it the name of a type in the current web?@> =
|
||||
<<Is it the name of a type in the current web?>>=
|
||||
language_type *str;
|
||||
LOOP_OVER(str, language_type) {
|
||||
if (Str::eq_insensitive(str->structure_name, text)) {
|
||||
|
@ -433,13 +434,13 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
}
|
||||
}
|
||||
|
||||
@<It refers unambiguously to a single section@> =
|
||||
<<It refers unambiguously to a single section>>=
|
||||
if (found_M == NULL) internal_error("could not locate M");
|
||||
if (search_CM) @<The section is a known colony member@>
|
||||
else @<The section is not in a known colony member@>;
|
||||
if (search_CM) <<The section is a known colony member>>
|
||||
else <<The section is not in a known colony member>>;
|
||||
return TRUE;
|
||||
|
||||
@<The section is a known colony member@> =
|
||||
<<The section is a known colony member>>=
|
||||
pathname *from = Filenames::up(for_HTML_file);
|
||||
pathname *to = search_CM->weave_path;
|
||||
Pathnames::relative_URL(url, from, to);
|
||||
|
@ -452,7 +453,7 @@ int Colonies::resolve_reference_in_weave_inner(text_stream *url, text_stream *ti
|
|||
guess it makes is that modules of the current web will be woven alongside
|
||||
the main one.
|
||||
|
||||
@<The section is not in a known colony member@> =
|
||||
<<The section is not in a known colony member>>=
|
||||
if (found_M == from_M) {
|
||||
Colonies::section_URL(url, found_Sm);
|
||||
} else {
|
||||
|
@ -462,9 +463,9 @@ the main one.
|
|||
WRITE_TO(title, " (in %S)", found_M->module_name);
|
||||
}
|
||||
|
||||
@h URL management.
|
||||
@ \section{URL management.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Colonies::link_URL(OUTPUT_STREAM, text_stream *link_text, filename *F) {
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Regexp::match(&mr, link_text, L" *//(%c+)// *"))
|
|
@ -10,15 +10,15 @@ editing the sections in the web.
|
|||
A ctags file is essentially just a list of identifiers called "tagnames",
|
||||
which are usually names of functions or data types, along with details of
|
||||
where they are defined in a program. Ctags files are almost never written by
|
||||
hand, but are instead generated by a tool such as the eponymous |ctags|. Here,
|
||||
hand, but are instead generated by a tool such as the eponymous [[ctags]]. Here,
|
||||
though, Inweb is going to do the generation, because it can make sense of the
|
||||
web structure of source code in a way which the |ctags| parser cannot.
|
||||
web structure of source code in a way which the [[ctags]] parser cannot.
|
||||
|
||||
The original |ctags| dates to 1992, and was devised by Ken Arnold. This was
|
||||
The original [[ctags]] dates to 1992, and was devised by Ken Arnold. This was
|
||||
much extended as Exuberant Ctags, by Darren Hiebert, which was then forked and
|
||||
re-maintained as Universal Ctags by Reza Jelveh and others. The result is
|
||||
nearly standard now, though as with a lot of early Unix infrastructure (compare
|
||||
|make|, for example), that standard design feels very antique: white space is
|
||||
[[make]], for example), that standard design feels very antique: white space is
|
||||
significant, filename extensions are not standard practice, and so on. See
|
||||
//Universal Ctags -> https://ctags.io// for more.[1]
|
||||
|
||||
|
@ -28,10 +28,10 @@ but it omits details of, e.g., exactly what characters must be escaped and how;
|
|||
what characters can legally be part of a tagname; and so on.
|
||||
|
||||
@ As mentioned above, Ctags go back to an age before filenames necessarily had
|
||||
extensions, and just as the defaukt make file is |makefile| and not |makefile.mk|,
|
||||
so the default Ctags file is called |tags| and not |tags.ctag|.
|
||||
extensions, and just as the defaukt make file is [[makefile]] and not [[makefile.mk]],
|
||||
so the default Ctags file is called [[tags]] and not [[tags.ctag]].
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Ctags::write(web *W, filename *F) {
|
||||
text_stream ctags_file;
|
||||
pathname *P = NULL;
|
||||
|
@ -44,41 +44,41 @@ void Ctags::write(web *W, filename *F) {
|
|||
text_stream *OUT = &ctags_file;
|
||||
if (STREAM_OPEN_TO_FILE(OUT, F, UTF8_ENC) == FALSE)
|
||||
Errors::fatal_with_file("unable to write ctags file", F);
|
||||
@<Write header@>;
|
||||
@<List defined constants@>;
|
||||
@<List structures@>;
|
||||
@<List functions@>;
|
||||
<<Write header>>;
|
||||
<<List defined constants>>;
|
||||
<<List structures>>;
|
||||
<<List functions>>;
|
||||
STREAM_CLOSE(OUT);
|
||||
}
|
||||
|
||||
@ Unless you really want to monkey with identifiers or filenames containing
|
||||
line break characters or tabs, a ctags file has a simple format to read or
|
||||
write: there's one tag on each line, and each line has three or more fields
|
||||
divided by tab characters. If we write | -> | for a tab, a line looks like:
|
||||
= (text)
|
||||
divided by tab characters. If we write [[ -> ]] for a tab, a line looks like:
|
||||
|
||||
tagname -> filename -> /find/;" -> more
|
||||
=
|
||||
|
||||
The stranded double-quote there is not a misprint. For example:
|
||||
= (text)
|
||||
|
||||
Frogs::spawn -> pond/Chapter 1/Amphibians.w -> /^void Frogs::spawn(species *S) {$/;" -> f
|
||||
=
|
||||
Here the tagname is |Frogs::spawn|. The filename |pond/Chapter 1/Amphibians.w|
|
||||
is the file defining this function. The |find| field is an EX-format command for
|
||||
finding the line in question: see below. Finally, the |more| field is actually
|
||||
|
||||
Here the tagname is [[Frogs::spawn]]. The filename [[pond/Chapter 1/Amphibians.w]]
|
||||
is the file defining this function. The [[find]] field is an EX-format command for
|
||||
finding the line in question: see below. Finally, the [[more]] field is actually
|
||||
a run of optional extra information, presented in a free-form sort of way, but
|
||||
we will use it only the simplest of ways. In this example it is just |f|,
|
||||
we will use it only the simplest of ways. In this example it is just [[f]],
|
||||
meaning "I am a function declaration".
|
||||
|
||||
The opening lines of the file, however, are usually metadata, i.e., describing the
|
||||
file itself and where it came from. In those lines, tagnames begin with |!_| and are
|
||||
called "pseudotags". The |filename| field is instead a value, while the |find|
|
||||
file itself and where it came from. In those lines, tagnames begin with [[!_]] and are
|
||||
called "pseudotags". The [[filename]] field is instead a value, while the [[find]]
|
||||
field is instead an optional comment.
|
||||
|
||||
The first two keys here are essential: the other three seem just to be good practice.
|
||||
These are the five keys which Universal |ctags| writes by default, so we'll follow
|
||||
These are the five keys which Universal [[ctags]] writes by default, so we'll follow
|
||||
suit.
|
||||
|
||||
@<Write header@> =
|
||||
<<Write header>>=
|
||||
WRITE("!_TAG_FILE_FORMAT\t2\t/extended format; --format=1 will not append ;\" to lines/\n");
|
||||
WRITE("!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/\n");
|
||||
WRITE("!_TAG_PROGRAM_AUTHOR\tGraham Nelson\t/graham.nelson@mod-langs.ox.ac.uk/\n");
|
||||
|
@ -88,9 +88,9 @@ suit.
|
|||
@ Having prudently opted to give the tags in an unsorted way, we're free to list
|
||||
them in any order convenient to us, and here goes.
|
||||
|
||||
The |more| field |d| says that a tagname is a defined constant:
|
||||
The [[more]] field [[d]] says that a tagname is a defined constant:
|
||||
|
||||
@<List defined constants@> =
|
||||
<<List defined constants>>=
|
||||
defined_constant *str;
|
||||
LOOP_OVER(str, defined_constant)
|
||||
if (str->at->owning_section->owning_web == W) {
|
||||
|
@ -101,11 +101,11 @@ The |more| field |d| says that a tagname is a defined constant:
|
|||
WRITE("\n");
|
||||
}
|
||||
|
||||
@ The |more| field |t| says that a tagname is a type, and we add a clarifying
|
||||
detail to say that it results from a |typedef struct|. (Note that |typeref|
|
||||
here, with an "r", is not a mistake. This is what Universal |ctags| calls it.)
|
||||
@ The [[more]] field [[t]] says that a tagname is a type, and we add a clarifying
|
||||
detail to say that it results from a [[typedef struct]]. (Note that [[typeref]]
|
||||
here, with an "r", is not a mistake. This is what Universal [[ctags]] calls it.)
|
||||
|
||||
@<List structures@> =
|
||||
<<List structures>>=
|
||||
language_type *str;
|
||||
LOOP_OVER(str, language_type)
|
||||
if (str->structure_header_at->owning_section->owning_web == W) {
|
||||
|
@ -116,9 +116,9 @@ here, with an "r", is not a mistake. This is what Universal |ctags| calls it.)
|
|||
WRITE("\n");
|
||||
}
|
||||
|
||||
@ The |more| field |f| says that a tagname is a function:
|
||||
@ The [[more]] field [[f]] says that a tagname is a function:
|
||||
|
||||
@<List functions@> =
|
||||
<<List functions>>=
|
||||
language_function *fn;
|
||||
LOOP_OVER(fn, language_function)
|
||||
if (fn->function_header_at->owning_section->owning_web == W) {
|
||||
|
@ -129,20 +129,20 @@ here, with an "r", is not a mistake. This is what Universal |ctags| calls it.)
|
|||
WRITE("\n");
|
||||
}
|
||||
|
||||
@ So, then, here we write the |filename| and |find| fields for a given
|
||||
source line |L| in our web. Note that:
|
||||
@ So, then, here we write the [[filename]] and [[find]] fields for a given
|
||||
source line [[L]] in our web. Note that:
|
||||
|
||||
(a) The filename must be given relative to the directory containing the tags
|
||||
file, so for us that will be the home directory of the web.
|
||||
|
||||
(b) The |find| field looks like a regular expression but is not one, despite
|
||||
the suggestive positional markers |^| and |$|. Note in particular that round
|
||||
(b) The [[find]] field looks like a regular expression but is not one, despite
|
||||
the suggestive positional markers [[^]] and [[$]]. Note in particular that round
|
||||
brackets and asterisk characters are not escaped, as they would be in a regex.
|
||||
The Ctags documentation is vague here but does note that |^| and |$| should
|
||||
The Ctags documentation is vague here but does note that [[^]] and [[$]] should
|
||||
be escaped only where they occur in the first or last positions. Tabs do
|
||||
not need to be escaped.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Ctags::write_line_ref(OUTPUT_STREAM, source_line *L, pathname *P) {
|
||||
TEMPORARY_TEXT(fn)
|
||||
WRITE_TO(fn, "%f", L->owning_section->md->source_file_for_section);
|
||||
|
@ -169,16 +169,16 @@ We could laboriously extract that from the hash table of reserved words
|
|||
(see //The Analyser//), but this is one of those times when life is short and
|
||||
memory is cheap. It's easier to keep a duplicate list.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct defined_constant {
|
||||
struct text_stream *name;
|
||||
struct source_line *at;
|
||||
CLASS_DEFINITION
|
||||
} defined_constant;
|
||||
|
||||
@ This is called for any |@d| or |@e| constant name, then:
|
||||
@ This is called for any [[@d]] or [[@e]] constant name, then:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Ctags::note_defined_constant(source_line *L, text_stream *name) {
|
||||
defined_constant *dc = CREATE(defined_constant);
|
||||
dc->name = Str::duplicate(name);
|
|
@ -4,7 +4,7 @@ Constructing a suitable gitignore file for a simple inweb project.
|
|||
|
||||
@ This is an extremely simple use of //foundation: Preprocessor//.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Git::write_gitignore(web *W, filename *prototype, filename *F) {
|
||||
linked_list *L = NEW_LINKED_LIST(preprocessor_macro);
|
||||
Preprocessor::new_macro(L, I"basics", NULL, Git::basics_expander, NULL);
|
||||
|
@ -18,7 +18,7 @@ void Git::write_gitignore(web *W, filename *prototype, filename *F) {
|
|||
@ Our one non-standard macro simply includes a file of standing material which
|
||||
is the same as the default .giscript file anyway:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Git::basics_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
filename *prototype = Filenames::in(path_to_inweb_materials, I"default.giscript");
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
Constructing a suitable makefile for a simple inweb project.
|
||||
|
||||
@h Preprocessing.
|
||||
@ \section{Preprocessing.}
|
||||
We will use //foundation: Preprocessor// with four special macros and one
|
||||
special loop construct.
|
||||
|
||||
For the syntax being worked through, see //Webs, Tangling and Weaving//.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::write(web *W, filename *prototype, filename *F, module_search *I,
|
||||
text_stream *platform) {
|
||||
linked_list *L = NEW_LINKED_LIST(preprocessor_macro);
|
||||
|
@ -33,7 +33,7 @@ void Makefiles::write(web *W, filename *prototype, filename *F, module_search *I
|
|||
Makefiles::components_expander, NULL);
|
||||
|
||||
makefile_specifics *specifics = CREATE(makefile_specifics);
|
||||
@<Initialise the specific data for makefile-preprocessing@>;
|
||||
<<Initialise the specific data for makefile-preprocessing>>;
|
||||
|
||||
text_stream *header = Str::new();
|
||||
WRITE_TO(header, "# This makefile was automatically written by inweb -makefile\n");
|
||||
|
@ -47,18 +47,18 @@ void Makefiles::write(web *W, filename *prototype, filename *F, module_search *I
|
|||
@ We will allow a makescript to declare "components" (webs, really), so we need
|
||||
a data structure to store those declarations in:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct makefile_specifics {
|
||||
struct web *for_web; /* if one has been set at the command line */
|
||||
struct dictionary *tools_dictionary; /* components with |type: tool| */
|
||||
struct dictionary *webs_dictionary; /* components with |type: web| */
|
||||
struct dictionary *modules_dictionary; /* components with |type: module| */
|
||||
struct dictionary *tools_dictionary; /* components with [[type: tool]] */
|
||||
struct dictionary *webs_dictionary; /* components with [[type: web]] */
|
||||
struct dictionary *modules_dictionary; /* components with [[type: module]] */
|
||||
struct module_search *search_path;
|
||||
struct text_stream *which_platform;
|
||||
CLASS_DEFINITION
|
||||
} makefile_specifics;
|
||||
|
||||
@<Initialise the specific data for makefile-preprocessing@> =
|
||||
<<Initialise the specific data for makefile-preprocessing>>=
|
||||
specifics->for_web = W;
|
||||
specifics->tools_dictionary = Dictionaries::new(16, FALSE);
|
||||
specifics->webs_dictionary = Dictionaries::new(16, FALSE);
|
||||
|
@ -66,9 +66,9 @@ typedef struct makefile_specifics {
|
|||
specifics->search_path = I;
|
||||
specifics->which_platform = platform;
|
||||
|
||||
@h The identity-settings expander.
|
||||
@ \section{The identity-settings expander.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::identity_settings_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
makefile_specifics *specifics = RETRIEVE_POINTER_makefile_specifics(PPS->specifics);
|
||||
|
@ -84,13 +84,13 @@ void Makefiles::identity_settings_expander(preprocessor_macro *mm, preprocessor_
|
|||
}
|
||||
}
|
||||
|
||||
@h The platform-settings expander.
|
||||
@ \section{The platform-settings expander.}
|
||||
We first scan Inweb's platform settings file for a definition line in the
|
||||
shape INWEBPLATFORM = PLATFORM, in order to find out what PLATFORM the make file
|
||||
will be used on. Then we splice in the appropriate file of standard definitions
|
||||
for that platform.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::platform_settings_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
makefile_specifics *specifics = RETRIEVE_POINTER_makefile_specifics(PPS->specifics);
|
||||
|
@ -123,9 +123,9 @@ void Makefiles::seek_INWEBPLATFORM(text_stream *line, text_file_position *tfp, v
|
|||
Regexp::dispose_of(&mr);
|
||||
}
|
||||
|
||||
@h The modify filename expander.
|
||||
@ \section{The modify filename expander.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::modify_filenames_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
text_stream *OUT = PPS->dest;
|
||||
|
@ -142,17 +142,17 @@ void Makefiles::modify_filenames_expander(preprocessor_macro *mm, preprocessor_s
|
|||
if (Characters::is_whitespace(c)) {
|
||||
if ((previous != '\\') && (quoted == FALSE)) boundary = TRUE;
|
||||
} else {
|
||||
if (boundary) @<Captured a name@>;
|
||||
if (boundary) <<Captured a name>>;
|
||||
boundary = FALSE;
|
||||
}
|
||||
PUT_TO(captured, c);
|
||||
previous = c;
|
||||
}
|
||||
@<Captured a name@>
|
||||
<<Captured a name>>
|
||||
DISCARD_TEXT(captured)
|
||||
}
|
||||
|
||||
@<Captured a name@> =
|
||||
<<Captured a name>>=
|
||||
Str::trim_white_space(captured);
|
||||
if (Str::len(captured) > 0) {
|
||||
int in_quotes = FALSE;
|
||||
|
@ -179,9 +179,9 @@ void Makefiles::modify_filenames_expander(preprocessor_macro *mm, preprocessor_s
|
|||
Str::clear(captured);
|
||||
}
|
||||
|
||||
@h The component expander.
|
||||
@ \section{The component expander.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::component_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
makefile_specifics *specifics = RETRIEVE_POINTER_makefile_specifics(PPS->specifics);
|
||||
|
@ -196,25 +196,25 @@ void Makefiles::component_expander(preprocessor_macro *mm, preprocessor_state *P
|
|||
if (Str::eq(category, I"tool")) {
|
||||
int marker = MAKEFILE_TOOL_MOM;
|
||||
dictionary *D = specifics->tools_dictionary;
|
||||
@<Add to dictionary@>;
|
||||
@<Derive some make symbols@>;
|
||||
<<Add to dictionary>>;
|
||||
<<Derive some make symbols>>;
|
||||
} else if (Str::eq(category, I"web")) {
|
||||
int marker = MAKEFILE_WEB_MOM;
|
||||
dictionary *D = specifics->webs_dictionary;
|
||||
@<Add to dictionary@>;
|
||||
@<Derive some make symbols@>;
|
||||
<<Add to dictionary>>;
|
||||
<<Derive some make symbols>>;
|
||||
} else if (Str::eq(category, I"module")) {
|
||||
int marker = MAKEFILE_MODULE_MOM;
|
||||
dictionary *D = specifics->modules_dictionary;
|
||||
@<Add to dictionary@>;
|
||||
@<Derive some make symbols@>;
|
||||
<<Add to dictionary>>;
|
||||
<<Derive some make symbols>>;
|
||||
} else {
|
||||
Errors::in_text_file("category should be 'tool', 'module' or 'web'", tfp);
|
||||
}
|
||||
PPS->last_line_was_blank = FALSE;
|
||||
}
|
||||
|
||||
@<Add to dictionary@> =
|
||||
<<Add to dictionary>>=
|
||||
web_md *Wm = Reader::load_web_md(Pathnames::from_text(path), NULL,
|
||||
specifics->search_path, TRUE);
|
||||
Wm->as_module->module_name = Str::duplicate(symbol);
|
||||
|
@ -223,15 +223,15 @@ void Makefiles::component_expander(preprocessor_macro *mm, preprocessor_state *P
|
|||
Dictionaries::create(D, symbol);
|
||||
Dictionaries::write_value(D, symbol, Wm);
|
||||
|
||||
@<Derive some make symbols@> =
|
||||
<<Derive some make symbols>>=
|
||||
WRITE("%SLEAF = %S\n", symbol, webname);
|
||||
WRITE("%SWEB = %S\n", symbol, path);
|
||||
WRITE("%SMAKER = $(%SWEB)/%S.mk\n", symbol, symbol, webname);
|
||||
WRITE("%SX = $(%SWEB)/Tangled/%S\n", symbol, symbol, webname);
|
||||
|
||||
@h The components loop construct.
|
||||
@ \section{The components loop construct.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::components_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
Preprocessor::set_loop_var_name(loop, I"SYMBOL");
|
||||
|
@ -240,19 +240,19 @@ void Makefiles::components_expander(preprocessor_macro *mm, preprocessor_state *
|
|||
if (Str::len(set) == 0) set = I"all";
|
||||
if (Str::eq(category, I"tool")) {
|
||||
int marker = MAKEFILE_TOOL_MOM;
|
||||
@<Make the web iterations@>;
|
||||
<<Make the web iterations>>;
|
||||
} else if (Str::eq(category, I"web")) {
|
||||
int marker = MAKEFILE_WEB_MOM;
|
||||
@<Make the web iterations@>;
|
||||
<<Make the web iterations>>;
|
||||
} else if (Str::eq(category, I"module")) {
|
||||
int marker = MAKEFILE_MODULE_MOM;
|
||||
@<Make the web iterations@>;
|
||||
<<Make the web iterations>>;
|
||||
} else {
|
||||
Errors::in_text_file("category should be 'tool', 'module' or 'web'", tfp);
|
||||
}
|
||||
}
|
||||
|
||||
@<Make the web iterations@> =
|
||||
<<Make the web iterations>>=
|
||||
module *M;
|
||||
LOOP_OVER(M, module) {
|
||||
if ((M->origin_marker == marker) &&
|
||||
|
@ -262,9 +262,9 @@ void Makefiles::components_expander(preprocessor_macro *mm, preprocessor_state *
|
|||
}
|
||||
}
|
||||
|
||||
@h The dependent-files expander.
|
||||
@ \section{The dependent-files expander.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::dependent_files_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
makefile_specifics *specifics = RETRIEVE_POINTER_makefile_specifics(PPS->specifics);
|
||||
|
@ -318,20 +318,20 @@ void Makefiles::dependent_files_expander(preprocessor_macro *mm, preprocessor_st
|
|||
}
|
||||
|
||||
@ This outputs a makefile pattern matching a bunch of web source code filenames:
|
||||
say, |inweb/Chapter\ %d/*.w|.
|
||||
say, [[inweb/Chapter\ %d/*.w]].
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::pattern(OUTPUT_STREAM, linked_list *L, filename *F) {
|
||||
dictionary *patterns_done = Dictionaries::new(16, TRUE);
|
||||
if (F) @<Add pattern for file F, if not already given@>;
|
||||
if (F) <<Add pattern for file F, if not already given>>;
|
||||
section_md *Sm;
|
||||
LOOP_OVER_LINKED_LIST(Sm, section_md, L) {
|
||||
filename *F = Sm->source_file_for_section;
|
||||
@<Add pattern for file F, if not already given@>;
|
||||
<<Add pattern for file F, if not already given>>;
|
||||
}
|
||||
}
|
||||
|
||||
@<Add pattern for file F, if not already given@> =
|
||||
<<Add pattern for file F, if not already given>>=
|
||||
pathname *P = Filenames::up(F);
|
||||
TEMPORARY_TEXT(leaf_pattern)
|
||||
WRITE_TO(leaf_pattern, "%S", Pathnames::directory_name(P));
|
||||
|
@ -361,7 +361,7 @@ void Makefiles::pattern(OUTPUT_STREAM, linked_list *L, filename *F) {
|
|||
bald statement really doesn't begin to go into how awkward makefiles can be
|
||||
when filenames have spaces in, but there we are.)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Makefiles::pathname_slashed(OUTPUT_STREAM, pathname *P) {
|
||||
TEMPORARY_TEXT(PT)
|
||||
WRITE_TO(PT, "%p", P);
|
|
@ -3,10 +3,10 @@
|
|||
To construct Readme and similar files.
|
||||
|
||||
@ This is a simple use of //foundation: Preprocessor//. Note that we use a
|
||||
non-standard comment syntax (i.e., |/| at start of line, not |#|) to avoid
|
||||
non-standard comment syntax (i.e., [[/]] at start of line, not [[#]]) to avoid
|
||||
colliding with Markdown's heading syntax.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Readme::write(filename *prototype, filename *F) {
|
||||
linked_list *L = NEW_LINKED_LIST(preprocessor_macro);
|
||||
preprocessor_macro *mm = Preprocessor::new_macro(L,
|
||||
|
@ -19,7 +19,7 @@ void Readme::write(filename *prototype, filename *F) {
|
|||
|
||||
@ And this is the one domain-specific macro:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Readme::bibliographic_expander(preprocessor_macro *mm, preprocessor_state *PPS,
|
||||
text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) {
|
||||
text_stream *datum = parameter_values[0];
|
||||
|
@ -37,7 +37,7 @@ also be a few other rather Inform-specific things; those have a more limited
|
|||
range of bibliographic data, just the version and date (and we will not
|
||||
assume that the version complies with any format).
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct writeme_asset {
|
||||
struct text_stream *name;
|
||||
struct web_md *if_web;
|
||||
|
@ -56,7 +56,7 @@ void Readme::write_var(text_stream *OUT, text_stream *program, text_stream *datu
|
|||
|
||||
@ That just leaves the business of inspecting assets to obtain their metadata.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
writeme_asset *Readme::find_asset(text_stream *program) {
|
||||
writeme_asset *A;
|
||||
LOOP_OVER(A, writeme_asset) if (Str::eq(program, A->name)) return A;
|
||||
|
@ -66,48 +66,48 @@ writeme_asset *Readme::find_asset(text_stream *program) {
|
|||
A->date = Str::new();
|
||||
A->version = Str::new();
|
||||
A->next_is_version = FALSE;
|
||||
@<Read in the asset@>;
|
||||
<<Read in the asset>>;
|
||||
return A;
|
||||
}
|
||||
|
||||
@<Read in the asset@> =
|
||||
<<Read in the asset>>=
|
||||
if (Str::ends_with_wide_string(program, L".i7x")) {
|
||||
@<Read in the extension file@>;
|
||||
<<Read in the extension file>>;
|
||||
} else {
|
||||
if (WebMetadata::directory_looks_like_a_web(Pathnames::from_text(program))) {
|
||||
A->if_web = WebMetadata::get_without_modules(Pathnames::from_text(program), NULL);
|
||||
} else {
|
||||
filename *I6_vn = Filenames::in(
|
||||
Pathnames::down(Pathnames::from_text(program), I"inform6"), I"header.h");
|
||||
if (TextFiles::exists(I6_vn)) @<Read in I6 source header file@>;
|
||||
if (TextFiles::exists(I6_vn)) <<Read in I6 source header file>>;
|
||||
filename *template_vn = Filenames::in(Pathnames::from_text(program), I"(manifest).txt");
|
||||
if (TextFiles::exists(template_vn)) @<Read in template manifest file@>;
|
||||
if (TextFiles::exists(template_vn)) <<Read in template manifest file>>;
|
||||
filename *rmt_vn = Filenames::in(Pathnames::from_text(program), I"README.txt");
|
||||
if (TextFiles::exists(rmt_vn)) @<Read in README file@>;
|
||||
if (TextFiles::exists(rmt_vn)) <<Read in README file>>;
|
||||
rmt_vn = Filenames::in(Pathnames::from_text(program), I"README.md");
|
||||
if (TextFiles::exists(rmt_vn)) @<Read in README file@>;
|
||||
if (TextFiles::exists(rmt_vn)) <<Read in README file>>;
|
||||
}
|
||||
}
|
||||
|
||||
@<Read in the extension file@> =
|
||||
<<Read in the extension file>>=
|
||||
TextFiles::read(Filenames::from_text(program), FALSE, "unable to read extension", TRUE,
|
||||
&Readme::extension_harvester, NULL, A);
|
||||
|
||||
@<Read in I6 source header file@> =
|
||||
<<Read in I6 source header file>>=
|
||||
TextFiles::read(I6_vn, FALSE, "unable to read header file from I6 source", TRUE,
|
||||
&Readme::header_harvester, NULL, A);
|
||||
|
||||
@<Read in template manifest file@> =
|
||||
<<Read in template manifest file>>=
|
||||
TextFiles::read(template_vn, FALSE, "unable to read manifest file from website template", TRUE,
|
||||
&Readme::template_harvester, NULL, A);
|
||||
|
||||
@<Read in README file@> =
|
||||
<<Read in README file>>=
|
||||
TextFiles::read(rmt_vn, FALSE, "unable to read README file from website template", TRUE,
|
||||
&Readme::readme_harvester, NULL, A);
|
||||
|
||||
@ The format for the contents section of a web is documented in Inweb.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Readme::extension_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||
writeme_asset *A = (writeme_asset *) state;
|
||||
match_results mr = Regexp::create_mr();
|
||||
|
@ -117,9 +117,9 @@ void Readme::extension_harvester(text_stream *text, text_file_position *tfp, voi
|
|||
Regexp::dispose_of(&mr);
|
||||
}
|
||||
|
||||
@ Explicit code to read from |header.h| in the Inform 6 repository.
|
||||
@ Explicit code to read from [[header.h]] in the Inform 6 repository.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Readme::header_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||
writeme_asset *A = (writeme_asset *) state;
|
||||
match_results mr = Regexp::create_mr();
|
||||
|
@ -133,7 +133,7 @@ void Readme::header_harvester(text_stream *text, text_file_position *tfp, void *
|
|||
|
||||
@ Explicit code to read from the manifest file of a website template.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Readme::template_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||
writeme_asset *A = (writeme_asset *) state;
|
||||
match_results mr = Regexp::create_mr();
|
||||
|
@ -147,9 +147,9 @@ void Readme::template_harvester(text_stream *text, text_file_position *tfp, void
|
|||
Regexp::dispose_of(&mr);
|
||||
}
|
||||
|
||||
@ And this is needed for |cheapglk| and |glulxe| in the Inform repository.
|
||||
@ And this is needed for [[cheapglk]] and [[glulxe]] in the Inform repository.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Readme::readme_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||
writeme_asset *A = (writeme_asset *) state;
|
||||
match_results mr = Regexp::create_mr();
|
Loading…
Reference in a new issue