1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-26 04:00:43 +03:00

Moved heading parser into inbuild

This commit is contained in:
Graham Nelson 2020-03-07 23:20:30 +00:00
parent efbc329e7c
commit 3fef66aaf0
23 changed files with 654 additions and 616 deletions

View file

@ -8,6 +8,7 @@ Setting up the use of this module.
@ To begin with, this module needs to allocate memory:
@e heading_MT
@e inform_kit_MT
@e inform_extension_MT
@e inform_kit_ittt_MT
@ -37,6 +38,7 @@ Setting up the use of this module.
@e control_structure_phrase_MT
=
ALLOCATE_INDIVIDUALLY(heading)
ALLOCATE_INDIVIDUALLY(inform_kit)
ALLOCATE_INDIVIDUALLY(inform_extension)
ALLOCATE_INDIVIDUALLY(inform_kit_ittt)
@ -102,9 +104,11 @@ void InbuildModule::start(void) {
@
@e EXTENSIONS_CENSUS_DA
@e HEADINGS_DA
@<Register this module's debugging log aspects@> =
Log::declare_aspect(EXTENSIONS_CENSUS_DA, L"extensions census", FALSE, FALSE);
Log::declare_aspect(HEADINGS_DA, L"headings", FALSE, FALSE);
@<Register this module's debugging log writers@> =
;

View file

@ -249,6 +249,10 @@ void Copies::write_problem(OUTPUT_STREAM, copy_error *CE) {
WRITE("extension has multiple 'ends here' sentences"); break;
case BadTitleSentence_SYNERROR:
WRITE("bibliographic sentence at the start is malformed"); break;
case UnknownLanguageElement_SYNERROR:
WRITE("unrecognised stipulation about Inform language elements"); break;
case UnknownVirtualMachine_SYNERROR:
WRITE("unrecognised stipulation about virtual machine"); break;
default:
WRITE("syntax error"); break;
}

View file

@ -345,7 +345,7 @@ LOG("Running rstf\n");
#ifdef CORE_MODULE
ParseTree::annotate_int(inclusions_heading, sentence_unparsed_ANNOT, FALSE);
ParseTree::annotate_int(inclusions_heading, heading_level_ANNOT, 0);
Sentences::Headings::declare(project->syntax_tree, inclusions_heading);
Headings::declare(project->syntax_tree, inclusions_heading);
#endif
int wc = lexer_wordcount, bwc = -1;
@ -384,7 +384,7 @@ LOG("Running rstf\n");
#ifdef CORE_MODULE
ParseTree::annotate_int(implicit_heading, sentence_unparsed_ANNOT, FALSE);
ParseTree::annotate_int(implicit_heading, heading_level_ANNOT, 0);
Sentences::Headings::declare(project->syntax_tree, implicit_heading);
Headings::declare(project->syntax_tree, implicit_heading);
#endif
ParseTree::pop_attachment_point(project->syntax_tree, l);

View file

@ -0,0 +1,565 @@
[Headings::] Headings.
To keep track of the hierarchy of headings and subheadings found
in the source text.
@ Headings in the source text correspond to |HEADING_NT| nodes in syntax
trees, and mostly occur when the user has explicitly typed a heading such as:
>> Part VII - The Ghost of the Aragon
The sentence-breaker called |Headings::declare| each time it found one of
these, but also when a new source file started, because a file boundary is
construed as beginning with a hidden "heading" of a higher rank than any
other, and the sentence-breaker made a corresponding HEADING node there
too. This is important because the doctrine is that each heading starts
afresh with a new hierarchy of lower-order headings: thus changing the Part
means we can start again with Chapter 1 if we like, and so on. Because each
source file starts with an implicit super-heading, each source file gets
its own independent hierarchy of Volume, and so on. But the convention is
also important because we need to be able to say that every word loaded
from disc ultimately falls under some heading, even if the source text as
typed by the designer does not obviously have any headings in it.
The hierarchy thus runs: File (0), Volume (1), Book (2), Part (3),
Chapter (4), Section (5). (The implementation below allows for even lower
levels of subheading, from 6 to 9, but Inform doesn't use them.) Every run
of Inform declares at least two File (0) headings, representing the start of
main text and the start of the Standard Rules, and these latter have a
couple of dozen headings themselves, so the typical number of headings
in a source text is 30 to 100.
@d NO_HEADING_LEVELS 10
@ Although it is implicit in the parse tree already, the heading structure
is not easy to deduce, and so in this section we build a much smaller tree
consisting just of the hierarchy of headings. The heading tree has nodes
made from the following structures:
=
typedef struct heading {
struct parse_node *sentence_declaring; /* if any: file starts are undeclared */
struct source_location start_location; /* first word under this heading is here */
int level; /* 0 for Volume (highest) to 5 for Section (lowest) */
int indentation; /* in a hierarchical listing */
int index_definitions_made_under_this; /* for instance, global variables made here? */
int for_release; /* include this material in a release version? */
int omit_material; /* if set, simply ignore all of this */
int use_with_or_without; /* if TRUE, use with the extension; if FALSE, without */
struct inbuild_work *for_use_with; /* e.g. "for use with ... by ..." */
struct wording in_place_of_text; /* e.g. "in place of ... in ... by ..." */
struct wording heading_text; /* once provisos have been stripped away */
struct noun *list_of_contents; /* tagged names defined under this */
struct noun *last_in_list_of_contents;
struct heading *parent_heading;
struct heading *child_heading;
struct heading *next_heading;
MEMORY_MANAGEMENT
} heading;
@ The headings and subheadings are formed into a tree in which each heading
contains its lesser-order headings. The pseudo-heading exists to be the root
of this tree; the entire text falls under it. It is not a real heading at all,
and has no "level" or "indentation" as such.
=
heading pseudo_heading; /* The entire source falls under this top-level heading */
@ As an example, a sequence in the primary source text of (Chapter I, Book
Two, Section 5, Chapter I, Section 1, Chapter III) would be formed up into
the heading tree:
|(the pseudo-heading) level -1, indentation -1|
| (File: Standard Rules) level 0, indentation 0|
| ...|
| (File: primary source text) level 0, indentation 0|
| Chapter I level 4, indentation 1|
| Book Two level 2, indentation 1|
| Section 5 level 5, indentation 2|
| Chapter I level 4, indentation 2|
| Section 1 level 5, indentation 3|
| Chapter III level 4, indentation 2|
Note that the level of a heading is not the same thing as its depth in this
tree, which we call the "indentation", and there is no simple relationship
between the two numbers. Clearly we want to start at the left margin. If a
new heading is subordinate to its predecessor (i.e., has higher level),
we want to indent further, but by the least amount needed -- a single tap step.
Adjacent equal-level headings are on a par with each other and should have
the same indentation. But when the new heading is lower level than its
predecessor (i.e., more important) then the indentation decreases to
match the last one equally important.
We can secure the last of those properties with a formal definition as
follows. The level $\ell_n$ of a heading depends only on its wording (or
source file origin), but the indentation of the $n$th heading, $i_n$,
depends on $(\ell_1, \ell_2, ..., \ell_n)$, the sequence of all levels so
far:
$$ i_n = i_m + 1 \qquad {\rm where}\qquad m = {\rm max} \lbrace j \mid 0\leq j < n, \ell_j < \ell_n \rbrace $$
where $\ell_0 = i_0 = -1$, so that this set always contains 0 and is
therefore not empty. We deduce that
(a) $i_1 = 0$ and thereafter $i_n \geq 0$, since $\ell_n$ is never negative again,
(b) if $\ell_k = \ell_{k+1}$ then $i_k = i_{k+1}$, since the set over which
the maximum is taken is the same,
(c) if $\ell_{k+1} > \ell_k$, a subheading of its predecessor, then
$i_{k+1} = i_k + 1$, a single tab step outward.
That establishes the other properties we wanted, and shows that $i_n$ is
indeed the number of tab steps we should be determining.
Note that to calculate $i_n$ we do not need the whole of $(\ell_1, ..., \ell_n)$:
we only need to remember the values of
$$ i_{m(K)},\qquad {\rm where}\qquad m(K) = {\rm max} \lbrace j \mid 0\leq j < n, \ell_j < K \rbrace $$
for each possible heading level $K=0, 1, ..., 9$. This requires much less
storage: we call it the "last indentation above level $K$".
This leads to the following algorithm when looking at the headings in any
individual file of source text: at the top of file,
|for (i=0; i<NO_HEADING_LEVELS; i++) last_indentation_above_level[i] = -1;|
Then parse for headings (they have an easily recognised lexical form); each
time one is found, work out its |level| as 1, ..., 5 for Volume down to Section,
and call:
|int find_indentation(int level) {|
| int i, ind = last_indentation_above_level[level] + 1;|
| for (i=level+1; i<NO_HEADING_LEVELS; i++)|
| last_indentation_above_level[i] = ind;|
| return ind;|
|}|
While this algorithm is trivially equivalent to finding the depth of a
heading in the tree which we are going to build anyway, it is worth noting
here for the benefit of anyone writing a tool to (let's say) typeset an
Inform source text with a table of contents, or provide a navigation
gadget in the user interface.
@ The primary source text, and indeed the source text in the extensions,
can make whatever headings they like: no sequence is illegal. It is not
for Inform to decide on behalf of the author that it is eccentric to place
Section C before Section B, for instance. The author might be doing so
deliberately, to put the Chariot-race before the Baths, say; and the
indexing means that it will be very apparent to the author what the heading
structure currently is, so mistakes are unlikely to last long. This is a
classic case where Inform trying to be too clever would annoy more often
than assist.
@ =
typedef struct name_resolution_data {
int heading_count; /* used when tallying up objects under their headings */
struct noun *next_under_heading; /* next in the list under that */
int search_score; /* used when searching nametags to parse names */
struct noun *next_to_search; /* similarly */
} name_resolution_data;
@ =
typedef struct contents_entry {
struct heading *heading_entered;
struct contents_entry *next;
MEMORY_MANAGEMENT
} contents_entry;
@h Declarations.
The heading tree is constructed all at once, after most of the sentence-breaking
is done, but since a few sentences can in principle be added later, we watch
for the remote chance of further headings being added, by keeping the following
flag:
=
int heading_tree_made_at_least_once = FALSE;
@ Now, then, the routine |Headings::declare| is called by the sentence-breaker
each time it constructs a new HEADING node. (Note that it is not called to
create the pseudo-heading, which does not come from a node.)
A level 0 heading has text (the first sentence which happens to be in the
new source file), but this has no significance other than its location,
and cannot contain information about releasing or about virtual machines.
=
int last_indentation_above_level[NO_HEADING_LEVELS], lial_made = FALSE;
inbuild_work *work_identified = NULL;
@
@d NEW_HEADING_HANDLER Headings::new_heading
=
int Headings::new_heading(parse_node_tree *T, parse_node *new) {
heading *h = Headings::declare(T, new);
#ifdef CORE_MODULE
ParseTree::set_embodying_heading(new, h);
#endif
return Headings::include_material(h);
}
heading *Headings::declare(parse_node_tree *T, parse_node *PN) {
heading *h = CREATE(heading);
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
h->list_of_contents = NULL; h->last_in_list_of_contents = NULL;
h->for_release = NOT_APPLICABLE; h->omit_material = FALSE;
h->index_definitions_made_under_this = TRUE;
h->use_with_or_without = NOT_APPLICABLE;
h->in_place_of_text = EMPTY_WORDING;
h->for_use_with = NULL;
if ((PN == NULL) || (Wordings::empty(ParseTree::get_text(PN))))
internal_error("heading at textless node");
if (ParseTree::get_type(PN) != HEADING_NT)
internal_error("declared a non-HEADING node as heading");
h->sentence_declaring = PN;
h->start_location = Wordings::location(ParseTree::get_text(PN));
h->level = ParseTree::int_annotation(PN, heading_level_ANNOT);
h->heading_text = EMPTY_WORDING;
if (h->level > 0) @<Parse heading text for release or other stipulations@>;
if ((h->level < 0) || (h->level >= NO_HEADING_LEVELS)) internal_error("impossible level");
@<Determine the indentation from the level@>;
LOGIF(HEADINGS, "Created heading $H\n", h);
if (heading_tree_made_at_least_once) Headings::make_tree();
return h;
}
@ This implements the indentation algorithm described above.
@<Determine the indentation from the level@> =
int i;
if (lial_made == FALSE) {
for (i=0; i<NO_HEADING_LEVELS; i++) last_indentation_above_level[i] = -1;
lial_made = TRUE;
}
h->indentation = last_indentation_above_level[h->level] + 1;
for (i=h->level+1; i<NO_HEADING_LEVELS; i++)
last_indentation_above_level[i] = h->indentation;
@h Parsing heading qualifiers.
@d PLATFORM_UNMET_HQ 0
@d PLATFORM_MET_HQ 1
@d NOT_FOR_RELEASE_HQ 2
@d FOR_RELEASE_ONLY_HQ 3
@d UNINDEXED_HQ 4
@d USE_WITH_HQ 5
@d USE_WITHOUT_HQ 6
@d IN_PLACE_OF_HQ 7
@<Parse heading text for release or other stipulations@> =
current_sentence = PN;
wording W = ParseTree::get_text(PN);
while (<heading-qualifier>(W)) {
switch (<<r>>) {
case PLATFORM_UNMET_HQ: h->omit_material = TRUE; break;
case NOT_FOR_RELEASE_HQ: h->for_release = FALSE; break;
case FOR_RELEASE_ONLY_HQ: h->for_release = TRUE; break;
case UNINDEXED_HQ: h->index_definitions_made_under_this = FALSE; break;
case USE_WITH_HQ: h->use_with_or_without = TRUE; break;
case USE_WITHOUT_HQ: h->use_with_or_without = FALSE; break;
case IN_PLACE_OF_HQ:
h->use_with_or_without = TRUE;
h->in_place_of_text = GET_RW(<extension-qualifier>, 1);
break;
}
W = GET_RW(<heading-qualifier>, 1);
}
h->heading_text = W;
h->for_use_with = work_identified;
@
@e UnknownLanguageElement_SYNERROR
@e UnknownVirtualMachine_SYNERROR
@ When a heading has been found, we repeatedly try to match it against
<heading-qualifier> to see if it ends with text telling us what to do with
the source text it governs. For example,
>> Section 21 - Frogs (unindexed) (not for Glulx)
would match twice, first registering the VM requirement, then the unindexedness.
It's an unfortunate historical quirk that the unbracketed qualifiers are
allowed; they should probably be withdrawn.
=
<heading-qualifier> ::=
... ( <bracketed-heading-qualifier> ) | ==> R[1]
... not for release | ==> NOT_FOR_RELEASE_HQ
... for release only | ==> FOR_RELEASE_ONLY_HQ
... unindexed ==> UNINDEXED_HQ
<bracketed-heading-qualifier> ::=
not for release | ==> NOT_FOR_RELEASE_HQ
for release only | ==> FOR_RELEASE_ONLY_HQ
unindexed | ==> UNINDEXED_HQ
<platform-qualifier> | ==> R[1]
<extension-qualifier> ==> R[1]
<platform-qualifier> ::=
for <platform-identifier> only | ==> (R[1])?PLATFORM_MET_HQ:PLATFORM_UNMET_HQ
not for <platform-identifier> ==> (R[1])?PLATFORM_UNMET_HQ:PLATFORM_MET_HQ
<platform-identifier> ::=
<language-element> language element | ==> R[1]
...... language element | ==> @<Issue PM_UnknownLanguageElement problem@>
<current-virtual-machine> | ==> R[1]
...... ==> @<Issue PM_UnknownVirtualMachine problem@>
<extension-qualifier> ::=
for use with <extension-identifier> | ==> USE_WITH_HQ
for use without <extension-identifier> | ==> USE_WITHOUT_HQ
not for use with <extension-identifier> | ==> USE_WITHOUT_HQ
in place of (<quoted-text>) in <extension-identifier> | ==> IN_PLACE_OF_HQ
in place of ...... in <extension-identifier> ==> IN_PLACE_OF_HQ
<extension-identifier> ::=
...... by ...... ==> @<Set for-use-with extension identifier@>
@<Issue PM_UnknownLanguageElement problem@> =
copy_error *CE = Copies::new_error(SYNTAX_CE, NULL);
CE->error_subcategory = UnknownLanguageElement_SYNERROR;
CE->details_node = current_sentence;
Copies::attach(sfsm_copy, CE);
@<Issue PM_UnknownVirtualMachine problem@> =
copy_error *CE = Copies::new_error(SYNTAX_CE, NULL);
CE->error_subcategory = UnknownVirtualMachine_SYNERROR;
CE->details_node = current_sentence;
Copies::attach(sfsm_copy, CE);
@<Set for-use-with extension identifier@> =
*X = R[0] + 4;
TEMPORARY_TEXT(exft);
TEMPORARY_TEXT(exfa);
wording TW = GET_RW(<extension-identifier>, 1);
wording AW = GET_RW(<extension-identifier>, 2);
WRITE_TO(exft, "%+W", TW);
WRITE_TO(exfa, "%+W", AW);
work_identified = Works::new(extension_genre, exft, exfa);
Works::add_to_database(work_identified, USEWITH_WDBC);
DISCARD_TEXT(exft);
DISCARD_TEXT(exfa);
@ =
<current-virtual-machine> internal {
if (<virtual-machine>(W)) {
*X = Compatibility::with((compatibility_specification *) <<rp>>, Inbuild::current_vm());
return TRUE;
} else {
*X = FALSE;
return FALSE;
}
}
@h The heading tree.
The headings were constructed above as freestanding nodes (except that the
pseudo-heading already existed): here, we assemble them into a tree
structure. Because we want to be able to call this more than once, perhaps
to make revisions if late news comes in of a new heading (see above), we
begin by removing any existing relationships between the heading nodes.
=
void Headings::make_tree(void) {
heading *h;
@<Reduce the whole heading tree to a pile of twigs@>;
LOOP_OVER(h, heading) {
@<If h is outside the tree, make it a child of the pseudo-heading@>;
@<Run through subsequent equal or subordinate headings to move them downward@>;
}
heading_tree_made_at_least_once = TRUE;
Headings::verify_heading_tree();
}
@ Note that the loop over headings below loops through all those which were
created by the memory manager: which is to say, all of them except for the
pseudo-heading, which was explicitly placed in static memory above.
@<Reduce the whole heading tree to a pile of twigs@> =
heading *h;
pseudo_heading.child_heading = NULL; pseudo_heading.parent_heading = NULL;
pseudo_heading.next_heading = NULL;
LOOP_OVER(h, heading) {
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
}
@ The idea of the heading loop is that when we place a heading, we also place
subsequent headings of lesser or equal status until we cannot do so any longer.
That means that if we reach h and find that it has no parent, it must be
subordinate to no earlier heading: thus, it must be attached to the pseudo-heading
at the top of the tree.
@<If h is outside the tree, make it a child of the pseudo-heading@> =
if (h->parent_heading == NULL)
Headings::make_child_heading(h, &pseudo_heading);
@ Note that the following could be summed up as "move subsequent headings as
deep in the tree as we can see they need to be from h's perspective alone".
This isn't always the final position. For instance, given the sequence
Volume 1, Chapter I, Section A, Chapter II, the tree is adjusted twice:
|when h = Volume 1: then when h = Chapter I:|
|Volume 1 Volume 1|
| Chapter I Chapter I|
| Section A Section A|
| Chapter II Chapter II|
since Section A is demoted twice, once by Volume 1, then by Chapter I.
(This algorithm would in principle be quadratic in the number of headings if
the possible depth of the tree were unbounded -- every heading might have to
demote every one of its successors -- but in fact because the depth is at
most 9, it runs in linear time.)
@<Run through subsequent equal or subordinate headings to move them downward@> =
heading *subseq;
for (subseq = NEXT_OBJECT(h, heading); /* start from the next heading in source */
(subseq) && (subseq->level >= h->level); /* for a run with level below or equal h */
subseq = NEXT_OBJECT(subseq, heading)) { /* in source declaration order */
if (subseq->level == h->level) { /* a heading of equal status ends the run... */
Headings::make_child_heading(subseq, h->parent_heading); break; /* ...and becomes h's sibling */
}
Headings::make_child_heading(subseq, h); /* all lesser headings in the run become h's children */
}
@ The above routine, then, calls |Headings::make_child_heading| to attach a heading
to the tree as a child of a given parent:
=
void Headings::make_child_heading(heading *ch, heading *pa) {
heading *former_pa = ch->parent_heading;
if (former_pa == pa) return;
@<Detach ch from the heading tree if it is already there@>;
ch->parent_heading = pa;
@<Add ch to the end of the list of children of pa@>;
}
@ If ch is present in the tree, it must have a parent, unless it is the
pseudo-heading: but the latter can never be moved, so it isn't. Therefore
we can remove ch by striking it out from the children list of the parent.
(Any children which ch has, grandchildren so to speak, come with it.)
@<Detach ch from the heading tree if it is already there@> =
if (former_pa) {
if (former_pa->child_heading == ch)
former_pa->child_heading = ch->next_heading;
else {
heading *sibling;
for (sibling = former_pa->child_heading; sibling; sibling = sibling->next_heading)
if (sibling->next_heading == ch) {
sibling->next_heading = ch->next_heading;
break;
}
}
}
ch->next_heading = NULL;
@ Two cases: the new parent is initially childless, or it isn't.
@<Add ch to the end of the list of children of pa@> =
heading *sibling;
if (pa->child_heading == NULL) pa->child_heading = ch;
else
for (sibling = pa->child_heading; sibling; sibling = sibling->next_heading)
if (sibling->next_heading == NULL) {
sibling->next_heading = ch;
break;
}
@h Verifying the heading tree.
We have now, in effect, computed the indentation value of each heading twice,
by two entirely different methods: first by the mathematical argument above,
then by observing that it is the depth in the heading tree. Seeing if
these two methods have given the same answer provides a convenient check on
our working.
=
int heading_tree_damaged = FALSE;
void Headings::verify_heading_tree(void) {
Headings::verify_heading_tree_r(&pseudo_heading, -1);
if (heading_tree_damaged) internal_error("heading tree failed to verify");
}
void Headings::verify_heading_tree_r(heading *h, int depth) {
if (h == NULL) return;
if ((h != &pseudo_heading) && (depth != h->indentation)) {
heading_tree_damaged = TRUE;
LOG("$H\n*** indentation should be %d ***\n", h, depth);
}
Headings::verify_heading_tree_r(h->child_heading, depth+1);
Headings::verify_heading_tree_r(h->next_heading, depth);
}
@h Miscellaneous heading services.
The first of these we have already seen in use: the sentence-breaker calls
it to ask whether sentences falling under the current heading should be
included in the active source text. (For instance, sentences under a
heading with the disclaimer "(for Glulx only)" will not be included
if the target virtual machine on this run of Inform is the Z-machine.)
=
int Headings::include_material(heading *h) {
int releasing = Inbuild::currently_releasing();
if ((h->for_release == TRUE) && (releasing == FALSE)) return FALSE;
if ((h->for_release == FALSE) && (releasing == TRUE)) return FALSE;
if (h->omit_material) return FALSE;
return TRUE;
}
int Headings::indexed(heading *h) {
if (h == NULL) return TRUE; /* definitions made nowhere are normally indexed */
return h->index_definitions_made_under_this;
}
@ A utility to do with the file of origin:
=
inform_extension *Headings::get_extension_containing(heading *h) {
if ((h == NULL) || (h->start_location.file_of_origin == NULL)) return NULL;
return Extensions::corresponding_to(h->start_location.file_of_origin);
}
@ Although File (0) headings do have text, contrary to the implication of
the routine here, this text is only what happens to be first in the file:
it isn't a heading actually typed by the user, which is all that we are
interested in for this purpose. So we send back a null word range.
=
wording Headings::get_text(heading *h) {
if ((h == NULL) || (h->level == 0)) return EMPTY_WORDING;
return h->heading_text;
}
@ This routine determines the (closest) heading to which a scrap of text
belongs, and is important since the parsing of noun phrases is affected by
that choice of heading (as we shall see): to Inform, headings provide something
analogous to the scope of local variables in a conventional programming
language.
Because every file has a File (0) heading registered at line 1, the loop
in the following routine is guaranteed to return a valid heading provided
the original source location is well formed (i.e., has a non-null source
file and a line number of at least 1).
=
heading *Headings::heading_of(source_location sl) {
heading *h;
if (sl.file_of_origin == NULL) return NULL;
LOOP_BACKWARDS_OVER(h, heading)
if ((sl.file_of_origin == h->start_location.file_of_origin) &&
(sl.line_number >= h->start_location.line_number)) return h;
internal_error("unable to determine the heading level of source material");
return NULL;
}
heading *Headings::of_wording(wording W) {
return Headings::heading_of(Wordings::location(W));
}

View file

@ -98,7 +98,7 @@ void SourceText::lexer_problem_handler(int err, text_stream *desc, wchar_t *word
@
@d EXTENSION_FILE_TYPE inbuild_copy
@d COPY_FILE_TYPE inbuild_copy
@
@ -157,15 +157,15 @@ source text.
@<Check we can begin an extension here@> =
switch (sfsm_extension_position) {
case 1: sfsm_extension_position++; break;
case 2: SYNTAX_PROBLEM_HANDLER(ExtMultipleBeginsHere_SYNERROR, W, sfsm_extension, 0); break;
case 3: SYNTAX_PROBLEM_HANDLER(ExtBeginsAfterEndsHere_SYNERROR, W, sfsm_extension, 0); break;
case 2: SYNTAX_PROBLEM_HANDLER(ExtMultipleBeginsHere_SYNERROR, W, sfsm_copy, 0); break;
case 3: SYNTAX_PROBLEM_HANDLER(ExtBeginsAfterEndsHere_SYNERROR, W, sfsm_copy, 0); break;
}
@<Check we can end an extension here@> =
switch (sfsm_extension_position) {
case 1: SYNTAX_PROBLEM_HANDLER(ExtEndsWithoutBegins_SYNERROR, W, sfsm_extension, 0); break;
case 1: SYNTAX_PROBLEM_HANDLER(ExtEndsWithoutBegins_SYNERROR, W, sfsm_copy, 0); break;
case 2: sfsm_extension_position++; break;
case 3: SYNTAX_PROBLEM_HANDLER(ExtMultipleEndsHere_SYNERROR, W, sfsm_extension, 0); break;
case 3: SYNTAX_PROBLEM_HANDLER(ExtMultipleEndsHere_SYNERROR, W, sfsm_copy, 0); break;
}
@<Detect a dividing sentence@> =

View file

@ -48,5 +48,6 @@ Chapter 5: Services for the Inform Compiler
Chapter 6: Handling Inform Source Text
Source Text
Headings
Control Structures
Virtual Machine Grammar

View file

@ -20,3 +20,5 @@ PM_ExtSpuriouslyContinues
PM_ExtMultipleEndsHere
PM_ExtMultipleBeginsHere
PM_ExtBeginsAfterEndsHere
PM_UnknownLanguageElement
PM_UnknownVirtualMachine

View file

@ -14,7 +14,6 @@ The following symbol is defined in every tool incorporating this module:
We need to itemise the structures we'll want to allocate:
@e bibliographic_datum_MT
@e heading_MT
@e phrase_MT
@e inference_MT
@e property_MT
@ -115,7 +114,6 @@ ALLOCATE_INDIVIDUALLY(equation_node)
ALLOCATE_INDIVIDUALLY(equation_symbol)
ALLOCATE_INDIVIDUALLY(equation)
ALLOCATE_INDIVIDUALLY(generalisation)
ALLOCATE_INDIVIDUALLY(heading)
ALLOCATE_INDIVIDUALLY(i6_inclusion_matter)
ALLOCATE_INDIVIDUALLY(i6_memory_setting)
ALLOCATE_INDIVIDUALLY(implication)
@ -289,7 +287,6 @@ we need to use the equivalent of traditional |malloc| and |calloc| routines.
@e DESCRIPTION_COMPILATION_DA
@e EXPRESSIONS_DA
@e FIGURE_CREATIONS_DA
@e HEADINGS_DA
@e IMPLICATIONS_DA
@e INFERENCES_DA
@e LOCAL_VARIABLES_DA
@ -334,7 +331,6 @@ we need to use the equivalent of traditional |malloc| and |calloc| routines.
Log::declare_aspect(DESCRIPTION_COMPILATION_DA, L"description compilation", FALSE, FALSE);
Log::declare_aspect(EXPRESSIONS_DA, L"expressions", FALSE, FALSE);
Log::declare_aspect(FIGURE_CREATIONS_DA, L"figure creations", FALSE, FALSE);
Log::declare_aspect(HEADINGS_DA, L"headings", FALSE, FALSE);
Log::declare_aspect(IMPLICATIONS_DA, L"implications", FALSE, TRUE);
Log::declare_aspect(INFERENCES_DA, L"inferences", FALSE, TRUE);
Log::declare_aspect(LOCAL_VARIABLES_DA, L"local variables", FALSE, FALSE);

View file

@ -126,7 +126,7 @@ most of these worker functions are in the |core| module, some are not.
Task::advance_stage_to(SEMANTIC_II_CSEQ, I"Semantic analysis II", -1);
BENCH(ParseTreeUsage::verify)
BENCH(Extensions::Files::check_versions)
BENCH(Sentences::Headings::make_tree)
BENCH(Headings::make_tree)
BENCH(Sentences::Headings::write_as_xml)
BENCH(Sentences::Headings::write_as_xml)
BENCH(Modules::traverse_to_define)

View file

@ -1423,8 +1423,8 @@ given extension:
=
int Tables::table_within(table *t, inform_extension *E) {
if (t->amendment_of) return FALSE;
heading *at_heading = Sentences::Headings::of_wording(ParseTree::get_text(t->table_created_at->source_table));
inform_extension *at_E = Sentences::Headings::get_extension_containing(at_heading);
heading *at_heading = Headings::of_wording(ParseTree::get_text(t->table_created_at->source_table));
inform_extension *at_E = Headings::get_extension_containing(at_heading);
if (E == at_E) return TRUE;
return FALSE;
}

View file

@ -266,6 +266,24 @@ void SourceProblems::issue_problems_arising(inbuild_copy *C) {
"the initial bibliographic sentence can only be a title in double-quotes",
"possibly followed with 'by' and the name of the author.");
break;
case UnknownLanguageElement_SYNERROR:
current_sentence = CE->details_node;
Problems::Issue::sentence_problem(
Task::syntax_tree(), _p_(PM_UnknownLanguageElement),
"this heading contains a stipulation about the current "
"Inform language definition which I can't understand",
"and should be something like '(for Glulx external files "
"language element only)'.");
break;
case UnknownVirtualMachine_SYNERROR:
current_sentence = CE->details_node;
Problems::Issue::sentence_problem(
Task::syntax_tree(), _p_(PM_UnknownVirtualMachine),
"this heading contains a stipulation about the Setting "
"for story file format which I can't understand",
"and should be something like '(for Z-machine version 5 "
"or 8 only)' or '(for Glulx only)'.");
break;
default:
internal_error("unknown syntax error");
}

View file

@ -32,11 +32,11 @@ void Phrases::Index::index_page_Phrasebook(OUTPUT_STREAM) {
continue;
/* and only if it is under an indexed heading */
heading *this_heading =
Sentences::Headings::of_wording(ParseTree::get_text(Phrases::declaration_node(ph)));
if (Sentences::Headings::indexed(this_heading) == FALSE) continue;
Headings::of_wording(ParseTree::get_text(Phrases::declaration_node(ph)));
if (Headings::indexed(this_heading) == FALSE) continue;
/* and only if that heading lies in the piece of source for this division */
inform_extension *this_extension =
Sentences::Headings::get_extension_containing(this_heading);
Headings::get_extension_containing(this_heading);
if (division == N) { /* skip phrase unless it's in the source text */
if (this_extension != NULL) continue;
} else { /* skip phrase unless it's defined in the extension for this division */
@ -80,7 +80,7 @@ the extension's name as a major subheading in our index.
each has a paragraph of its own.
@<Mark a subdivision in the Phrasebook@> =
wording HW = Sentences::Headings::get_text(this_heading);
wording HW = Headings::get_text(this_heading);
if (Wordings::nonempty(HW)) {
if (pass == 1) @<Strip away bracketed matter in the heading name@>;
if (Extensions::is_standard(this_extension))

View file

@ -824,10 +824,10 @@ void NonlocalVariables::index_all(OUTPUT_STREAM) {
}
@<Index a regular variable@> =
definition_area = Sentences::Headings::of_wording(nlv->name);
if (Sentences::Headings::indexed(definition_area) == FALSE) continue;
definition_area = Headings::of_wording(nlv->name);
if (Headings::indexed(definition_area) == FALSE) continue;
if (definition_area != current_area) {
wording W = Sentences::Headings::get_text(definition_area);
wording W = Headings::get_text(definition_area);
HTML_CLOSE("p");
HTML_OPEN("p");
if (Wordings::nonempty(W)) Phrases::Index::index_definition_area(OUT, W, FALSE);

View file

@ -3,557 +3,6 @@
To keep track of the hierarchy of headings and subheadings found
in the source text.
@h Definitions.
@ To take up the narrative of how Inform runs once more, we have read the
source text in from the primary source file and from the extensions it
requested: nothing more will be supplied from disc, and the whole combined
text is arranged as a string of sentence nodes, each direct children of the
root. In this section, we are concerned with HEADING nodes.
Most of these occur when the user has explicitly typed a heading such as:
>> Part VII - The Ghost of the Aragon
The sentence-breaker called |Sentences::Headings::declare| each time it found one of
these, but also when a new source file started, because a file boundary is
construed as beginning with a hidden "heading" of a higher rank than any
other, and the sentence-breaker made a corresponding HEADING node there
too. This is important because the doctrine is that each heading starts
afresh with a new hierarchy of lower-order headings: thus changing the Part
means we can start again with Chapter 1 if we like, and so on. Because each
source file starts with an implicit super-heading, each source file gets
its own independent hierarchy of Volume, and so on. But the convention is
also important because we need to be able to say that every word loaded
from disc ultimately falls under some heading, even if the source text as
typed by the designer does not obviously have any headings in it.
The hierarchy thus runs: File (0), Volume (1), Book (2), Part (3),
Chapter (4), Section (5). (The implementation below allows for even lower
levels of subheading, from 6 to 9, but Inform doesn't use them.) Every run
of Inform declares at least two File (0) headings, representing the start of
main text and the start of the Standard Rules, and these latter have a
couple of dozen headings themselves, so the typical number of headings
in a source text is 30 to 100.
@d NO_HEADING_LEVELS 10
@ Although it is implicit in the parse tree already, the heading structure
is not easy to deduce, and so in this section we build a much smaller tree
consisting just of the hierarchy of headings. The heading tree has nodes
made from the following structures:
=
typedef struct heading {
struct parse_node *sentence_declaring; /* if any: file starts are undeclared */
struct source_location start_location; /* first word under this heading is here */
int level; /* 0 for Volume (highest) to 5 for Section (lowest) */
int indentation; /* in a hierarchical listing */
int index_definitions_made_under_this; /* for instance, global variables made here? */
int for_release; /* include this material in a release version? */
int omit_material; /* if set, simply ignore all of this */
int use_with_or_without; /* if TRUE, use with the extension; if FALSE, without */
struct inbuild_work *for_use_with; /* e.g. "for use with ... by ..." */
struct wording in_place_of_text; /* e.g. "in place of ... in ... by ..." */
struct wording heading_text; /* once provisos have been stripped away */
struct noun *list_of_contents; /* tagged names defined under this */
struct noun *last_in_list_of_contents;
struct heading *parent_heading;
struct heading *child_heading;
struct heading *next_heading;
MEMORY_MANAGEMENT
} heading;
@ The headings and subheadings are formed into a tree in which each heading
contains its lesser-order headings. The pseudo-heading exists to be the root
of this tree; the entire text falls under it. It is not a real heading at all,
and has no "level" or "indentation" as such.
=
heading pseudo_heading; /* The entire source falls under this top-level heading */
@ As an example, a sequence in the primary source text of (Chapter I, Book
Two, Section 5, Chapter I, Section 1, Chapter III) would be formed up into
the heading tree:
|(the pseudo-heading) level -1, indentation -1|
| (File: Standard Rules) level 0, indentation 0|
| ...|
| (File: primary source text) level 0, indentation 0|
| Chapter I level 4, indentation 1|
| Book Two level 2, indentation 1|
| Section 5 level 5, indentation 2|
| Chapter I level 4, indentation 2|
| Section 1 level 5, indentation 3|
| Chapter III level 4, indentation 2|
Note that the level of a heading is not the same thing as its depth in this
tree, which we call the "indentation", and there is no simple relationship
between the two numbers. Clearly we want to start at the left margin. If a
new heading is subordinate to its predecessor (i.e., has higher level),
we want to indent further, but by the least amount needed -- a single tap step.
Adjacent equal-level headings are on a par with each other and should have
the same indentation. But when the new heading is lower level than its
predecessor (i.e., more important) then the indentation decreases to
match the last one equally important.
We can secure the last of those properties with a formal definition as
follows. The level $\ell_n$ of a heading depends only on its wording (or
source file origin), but the indentation of the $n$th heading, $i_n$,
depends on $(\ell_1, \ell_2, ..., \ell_n)$, the sequence of all levels so
far:
$$ i_n = i_m + 1 \qquad {\rm where}\qquad m = {\rm max} \lbrace j \mid 0\leq j < n, \ell_j < \ell_n \rbrace $$
where $\ell_0 = i_0 = -1$, so that this set always contains 0 and is
therefore not empty. We deduce that
(a) $i_1 = 0$ and thereafter $i_n \geq 0$, since $\ell_n$ is never negative again,
(b) if $\ell_k = \ell_{k+1}$ then $i_k = i_{k+1}$, since the set over which
the maximum is taken is the same,
(c) if $\ell_{k+1} > \ell_k$, a subheading of its predecessor, then
$i_{k+1} = i_k + 1$, a single tab step outward.
That establishes the other properties we wanted, and shows that $i_n$ is
indeed the number of tab steps we should be determining.
Note that to calculate $i_n$ we do not need the whole of $(\ell_1, ..., \ell_n)$:
we only need to remember the values of
$$ i_{m(K)},\qquad {\rm where}\qquad m(K) = {\rm max} \lbrace j \mid 0\leq j < n, \ell_j < K \rbrace $$
for each possible heading level $K=0, 1, ..., 9$. This requires much less
storage: we call it the "last indentation above level $K$".
This leads to the following algorithm when looking at the headings in any
individual file of source text: at the top of file,
|for (i=0; i<NO_HEADING_LEVELS; i++) last_indentation_above_level[i] = -1;|
Then parse for headings (they have an easily recognised lexical form); each
time one is found, work out its |level| as 1, ..., 5 for Volume down to Section,
and call:
|int find_indentation(int level) {|
| int i, ind = last_indentation_above_level[level] + 1;|
| for (i=level+1; i<NO_HEADING_LEVELS; i++)|
| last_indentation_above_level[i] = ind;|
| return ind;|
|}|
While this algorithm is trivially equivalent to finding the depth of a
heading in the tree which we are going to build anyway, it is worth noting
here for the benefit of anyone writing a tool to (let's say) typeset an
Inform source text with a table of contents, or provide a navigation
gadget in the user interface.
@ The primary source text, and indeed the source text in the extensions,
can make whatever headings they like: no sequence is illegal. It is not
for Inform to decide on behalf of the author that it is eccentric to place
Section C before Section B, for instance. The author might be doing so
deliberately, to put the Chariot-race before the Baths, say; and the
indexing means that it will be very apparent to the author what the heading
structure currently is, so mistakes are unlikely to last long. This is a
classic case where Inform trying to be too clever would annoy more often
than assist.
@ =
typedef struct name_resolution_data {
int heading_count; /* used when tallying up objects under their headings */
struct noun *next_under_heading; /* next in the list under that */
int search_score; /* used when searching nametags to parse names */
struct noun *next_to_search; /* similarly */
} name_resolution_data;
@ =
typedef struct contents_entry {
struct heading *heading_entered;
struct contents_entry *next;
MEMORY_MANAGEMENT
} contents_entry;
@h Declarations.
The heading tree is constructed all at once, after most of the sentence-breaking
is done, but since a few sentences can in principle be added later, we watch
for the remote chance of further headings being added, by keeping the following
flag:
=
int heading_tree_made_at_least_once = FALSE;
@ Now, then, the routine |Sentences::Headings::declare| is called by the sentence-breaker
each time it constructs a new HEADING node. (Note that it is not called to
create the pseudo-heading, which does not come from a node.)
A level 0 heading has text (the first sentence which happens to be in the
new source file), but this has no significance other than its location,
and cannot contain information about releasing or about virtual machines.
=
int last_indentation_above_level[NO_HEADING_LEVELS], lial_made = FALSE;
inbuild_work *work_identified = NULL;
heading *Sentences::Headings::declare(parse_node_tree *T, parse_node *PN) {
heading *h = CREATE(heading);
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
h->list_of_contents = NULL; h->last_in_list_of_contents = NULL;
h->for_release = NOT_APPLICABLE; h->omit_material = FALSE;
h->index_definitions_made_under_this = TRUE;
h->use_with_or_without = NOT_APPLICABLE;
h->in_place_of_text = EMPTY_WORDING;
h->for_use_with = NULL;
if ((PN == NULL) || (Wordings::empty(ParseTree::get_text(PN))))
internal_error("heading at textless node");
internal_error_if_node_type_wrong(T, PN, HEADING_NT);
h->sentence_declaring = PN;
h->start_location = Wordings::location(ParseTree::get_text(PN));
h->level = ParseTree::int_annotation(PN, heading_level_ANNOT);
h->heading_text = EMPTY_WORDING;
if (h->level > 0) @<Parse heading text for release or other stipulations@>;
if ((h->level < 0) || (h->level >= NO_HEADING_LEVELS)) internal_error("impossible level");
@<Determine the indentation from the level@>;
LOGIF(HEADINGS, "Created heading $H\n", h);
if (heading_tree_made_at_least_once) Sentences::Headings::make_tree();
return h;
}
@ This implements the indentation algorithm described above.
@<Determine the indentation from the level@> =
int i;
if (lial_made == FALSE) {
for (i=0; i<NO_HEADING_LEVELS; i++) last_indentation_above_level[i] = -1;
lial_made = TRUE;
}
h->indentation = last_indentation_above_level[h->level] + 1;
for (i=h->level+1; i<NO_HEADING_LEVELS; i++)
last_indentation_above_level[i] = h->indentation;
@h Parsing heading qualifiers.
@d PLATFORM_UNMET_HQ 0
@d PLATFORM_MET_HQ 1
@d NOT_FOR_RELEASE_HQ 2
@d FOR_RELEASE_ONLY_HQ 3
@d UNINDEXED_HQ 4
@d USE_WITH_HQ 5
@d USE_WITHOUT_HQ 6
@d IN_PLACE_OF_HQ 7
@<Parse heading text for release or other stipulations@> =
current_sentence = PN;
wording W = ParseTree::get_text(PN);
while (<heading-qualifier>(W)) {
switch (<<r>>) {
case PLATFORM_UNMET_HQ: h->omit_material = TRUE; break;
case NOT_FOR_RELEASE_HQ: h->for_release = FALSE; break;
case FOR_RELEASE_ONLY_HQ: h->for_release = TRUE; break;
case UNINDEXED_HQ: h->index_definitions_made_under_this = FALSE; break;
case USE_WITH_HQ: h->use_with_or_without = TRUE; break;
case USE_WITHOUT_HQ: h->use_with_or_without = FALSE; break;
case IN_PLACE_OF_HQ:
h->use_with_or_without = TRUE;
h->in_place_of_text = GET_RW(<extension-qualifier>, 1);
break;
}
W = GET_RW(<heading-qualifier>, 1);
}
h->heading_text = W;
h->for_use_with = work_identified;
@ When a heading has been found, we repeatedly try to match it against
<heading-qualifier> to see if it ends with text telling us what to do with
the source text it governs. For example,
>> Section 21 - Frogs (unindexed) (not for Glulx)
would match twice, first registering the VM requirement, then the unindexedness.
It's an unfortunate historical quirk that the unbracketed qualifiers are
allowed; they should probably be withdrawn.
=
<heading-qualifier> ::=
... ( <bracketed-heading-qualifier> ) | ==> R[1]
... not for release | ==> NOT_FOR_RELEASE_HQ
... for release only | ==> FOR_RELEASE_ONLY_HQ
... unindexed ==> UNINDEXED_HQ
<bracketed-heading-qualifier> ::=
not for release | ==> NOT_FOR_RELEASE_HQ
for release only | ==> FOR_RELEASE_ONLY_HQ
unindexed | ==> UNINDEXED_HQ
<platform-qualifier> | ==> R[1]
<extension-qualifier> ==> R[1]
<platform-qualifier> ::=
for <platform-identifier> only | ==> (R[1])?PLATFORM_MET_HQ:PLATFORM_UNMET_HQ
not for <platform-identifier> ==> (R[1])?PLATFORM_UNMET_HQ:PLATFORM_MET_HQ
<platform-identifier> ::=
<language-element> language element | ==> R[1]
...... language element | ==> @<Issue PM_UnknownLanguageElement problem@>
<current-virtual-machine> | ==> R[1]
...... ==> @<Issue PM_UnknownVirtualMachine problem@>
<extension-qualifier> ::=
for use with <extension-identifier> | ==> USE_WITH_HQ
for use without <extension-identifier> | ==> USE_WITHOUT_HQ
not for use with <extension-identifier> | ==> USE_WITHOUT_HQ
in place of (<quoted-text>) in <extension-identifier> | ==> IN_PLACE_OF_HQ
in place of ...... in <extension-identifier> ==> IN_PLACE_OF_HQ
<extension-identifier> ::=
...... by ...... ==> @<Set for-use-with extension identifier@>
@<Issue PM_UnknownLanguageElement problem@> =
Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_UnknownLanguageElement),
"this heading contains a stipulation about the current "
"Inform language definition which I can't understand",
"and should be something like '(for Glulx external files "
"language element only)'.");
@<Issue PM_UnknownVirtualMachine problem@> =
Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_UnknownVirtualMachine),
"this heading contains a stipulation about the Setting "
"for story file format which I can't understand",
"and should be something like '(for Z-machine version 5 "
"or 8 only)' or '(for Glulx only)'.");
@<Set for-use-with extension identifier@> =
*X = R[0] + 4;
TEMPORARY_TEXT(exft);
TEMPORARY_TEXT(exfa);
wording TW = GET_RW(<extension-identifier>, 1);
wording AW = GET_RW(<extension-identifier>, 2);
WRITE_TO(exft, "%+W", TW);
WRITE_TO(exfa, "%+W", AW);
work_identified = Works::new(extension_genre, exft, exfa);
Works::add_to_database(work_identified, USEWITH_WDBC);
DISCARD_TEXT(exft);
DISCARD_TEXT(exfa);
@ =
<current-virtual-machine> internal {
if (<virtual-machine>(W)) {
*X = Compatibility::with((compatibility_specification *) <<rp>>, Inbuild::current_vm());
return TRUE;
} else {
*X = FALSE;
return FALSE;
}
}
@h The heading tree.
The headings were constructed above as freestanding nodes (except that the
pseudo-heading already existed): here, we assemble them into a tree
structure. Because we want to be able to call this more than once, perhaps
to make revisions if late news comes in of a new heading (see above), we
begin by removing any existing relationships between the heading nodes.
=
void Sentences::Headings::make_tree(void) {
heading *h;
@<Reduce the whole heading tree to a pile of twigs@>;
LOOP_OVER(h, heading) {
@<If h is outside the tree, make it a child of the pseudo-heading@>;
@<Run through subsequent equal or subordinate headings to move them downward@>;
}
heading_tree_made_at_least_once = TRUE;
Sentences::Headings::verify_heading_tree();
}
@ Note that the loop over headings below loops through all those which were
created by the memory manager: which is to say, all of them except for the
pseudo-heading, which was explicitly placed in static memory above.
@<Reduce the whole heading tree to a pile of twigs@> =
heading *h;
pseudo_heading.child_heading = NULL; pseudo_heading.parent_heading = NULL;
pseudo_heading.next_heading = NULL;
LOOP_OVER(h, heading) {
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
}
@ The idea of the heading loop is that when we place a heading, we also place
subsequent headings of lesser or equal status until we cannot do so any longer.
That means that if we reach h and find that it has no parent, it must be
subordinate to no earlier heading: thus, it must be attached to the pseudo-heading
at the top of the tree.
@<If h is outside the tree, make it a child of the pseudo-heading@> =
if (h->parent_heading == NULL)
Sentences::Headings::make_child_heading(h, &pseudo_heading);
@ Note that the following could be summed up as "move subsequent headings as
deep in the tree as we can see they need to be from h's perspective alone".
This isn't always the final position. For instance, given the sequence
Volume 1, Chapter I, Section A, Chapter II, the tree is adjusted twice:
|when h = Volume 1: then when h = Chapter I:|
|Volume 1 Volume 1|
| Chapter I Chapter I|
| Section A Section A|
| Chapter II Chapter II|
since Section A is demoted twice, once by Volume 1, then by Chapter I.
(This algorithm would in principle be quadratic in the number of headings if
the possible depth of the tree were unbounded -- every heading might have to
demote every one of its successors -- but in fact because the depth is at
most 9, it runs in linear time.)
@<Run through subsequent equal or subordinate headings to move them downward@> =
heading *subseq;
for (subseq = NEXT_OBJECT(h, heading); /* start from the next heading in source */
(subseq) && (subseq->level >= h->level); /* for a run with level below or equal h */
subseq = NEXT_OBJECT(subseq, heading)) { /* in source declaration order */
if (subseq->level == h->level) { /* a heading of equal status ends the run... */
Sentences::Headings::make_child_heading(subseq, h->parent_heading); break; /* ...and becomes h's sibling */
}
Sentences::Headings::make_child_heading(subseq, h); /* all lesser headings in the run become h's children */
}
@ The above routine, then, calls |Sentences::Headings::make_child_heading| to attach a heading
to the tree as a child of a given parent:
=
void Sentences::Headings::make_child_heading(heading *ch, heading *pa) {
heading *former_pa = ch->parent_heading;
if (former_pa == pa) return;
@<Detach ch from the heading tree if it is already there@>;
ch->parent_heading = pa;
@<Add ch to the end of the list of children of pa@>;
}
@ If ch is present in the tree, it must have a parent, unless it is the
pseudo-heading: but the latter can never be moved, so it isn't. Therefore
we can remove ch by striking it out from the children list of the parent.
(Any children which ch has, grandchildren so to speak, come with it.)
@<Detach ch from the heading tree if it is already there@> =
if (former_pa) {
if (former_pa->child_heading == ch)
former_pa->child_heading = ch->next_heading;
else {
heading *sibling;
for (sibling = former_pa->child_heading; sibling; sibling = sibling->next_heading)
if (sibling->next_heading == ch) {
sibling->next_heading = ch->next_heading;
break;
}
}
}
ch->next_heading = NULL;
@ Two cases: the new parent is initially childless, or it isn't.
@<Add ch to the end of the list of children of pa@> =
heading *sibling;
if (pa->child_heading == NULL) pa->child_heading = ch;
else
for (sibling = pa->child_heading; sibling; sibling = sibling->next_heading)
if (sibling->next_heading == NULL) {
sibling->next_heading = ch;
break;
}
@h Verifying the heading tree.
We have now, in effect, computed the indentation value of each heading twice,
by two entirely different methods: first by the mathematical argument above,
then by observing that it is the depth in the heading tree. Seeing if
these two methods have given the same answer provides a convenient check on
our working.
=
int heading_tree_damaged = FALSE;
void Sentences::Headings::verify_heading_tree(void) {
Sentences::Headings::verify_heading_tree_recursively(&pseudo_heading, -1);
if (heading_tree_damaged) internal_error("heading tree failed to verify");
}
void Sentences::Headings::verify_heading_tree_recursively(heading *h, int depth) {
if (h == NULL) return;
if ((h != &pseudo_heading) && (depth != h->indentation)) {
heading_tree_damaged = TRUE;
LOG("$H\n*** indentation should be %d ***\n", h, depth);
}
Sentences::Headings::verify_heading_tree_recursively(h->child_heading, depth+1);
Sentences::Headings::verify_heading_tree_recursively(h->next_heading, depth);
}
@h Miscellaneous heading services.
The first of these we have already seen in use: the sentence-breaker calls
it to ask whether sentences falling under the current heading should be
included in the active source text. (For instance, sentences under a
heading with the disclaimer "(for Glulx only)" will not be included
if the target virtual machine on this run of Inform is the Z-machine.)
=
int Sentences::Headings::include_material(heading *h) {
int releasing = Inbuild::currently_releasing();
if ((h->for_release == TRUE) && (releasing == FALSE)) return FALSE;
if ((h->for_release == FALSE) && (releasing == TRUE)) return FALSE;
if (h->omit_material) return FALSE;
return TRUE;
}
int Sentences::Headings::indexed(heading *h) {
if (h == NULL) return TRUE; /* definitions made nowhere are normally indexed */
return h->index_definitions_made_under_this;
}
@ A utility to do with the file of origin:
=
inform_extension *Sentences::Headings::get_extension_containing(heading *h) {
if ((h == NULL) || (h->start_location.file_of_origin == NULL)) return NULL;
return Extensions::corresponding_to(h->start_location.file_of_origin);
}
@ Although File (0) headings do have text, contrary to the implication of
the routine here, this text is only what happens to be first in the file:
it isn't a heading actually typed by the user, which is all that we are
interested in for this purpose. So we send back a null word range.
=
wording Sentences::Headings::get_text(heading *h) {
if ((h == NULL) || (h->level == 0)) return EMPTY_WORDING;
return h->heading_text;
}
@ This routine determines the (closest) heading to which a scrap of text
belongs, and is important since the parsing of noun phrases is affected by
that choice of heading (as we shall see): to Inform, headings provide something
analogous to the scope of local variables in a conventional programming
language.
Because every file has a File (0) heading registered at line 1, the loop
in the following routine is guaranteed to return a valid heading provided
the original source location is well formed (i.e., has a non-null source
file and a line number of at least 1).
=
heading *Sentences::Headings::heading_of(source_location sl) {
heading *h;
if (sl.file_of_origin == NULL) return NULL;
LOOP_BACKWARDS_OVER(h, heading)
if ((sl.file_of_origin == h->start_location.file_of_origin) &&
(sl.line_number >= h->start_location.line_number)) return h;
internal_error("unable to determine the heading level of source material");
return NULL;
}
heading *Sentences::Headings::of_wording(wording W) {
return Sentences::Headings::heading_of(Wordings::location(W));
}
@h Headings with extension dependencies.
If the content under a heading depended on the VM not in use, or was marked
not for release in a release run, we were able to exclude it just by
@ -600,7 +49,7 @@ void Sentences::Headings::satisfy_individual_heading_dependency(heading *h) {
if ((Wordings::nonempty(h2->heading_text)) &&
(Wordings::match_perhaps_quoted(S, h2->heading_text)) &&
(Works::match(
Sentences::Headings::get_extension_containing(h2)->as_copy->edition->work, work))) {
Headings::get_extension_containing(h2)->as_copy->edition->work, work))) {
found = TRUE;
if (h->level != h2->level)
@<Can't replace heading unless level matches@>;
@ -735,7 +184,7 @@ name_resolution_data *Sentences::Headings::name_resolution_data(noun *t) {
int no_tags_attached = 0;
void Sentences::Headings::attach_noun(noun *new_tag) {
if (current_sentence == NULL) return;
heading *h = Sentences::Headings::of_wording(ParseTree::get_text(current_sentence));
heading *h = Headings::of_wording(ParseTree::get_text(current_sentence));
if (h == NULL) return;
no_tags_attached++;
name_resolution_data *nrd = Sentences::Headings::name_resolution_data(new_tag);
@ -887,7 +336,7 @@ current sentence:
if ((current_sentence == NULL) || (Wordings::empty(ParseTree::get_text(current_sentence))))
internal_error("cannot establish position P: there is no current sentence");
source_location position_P = Wordings::location(ParseTree::get_text(current_sentence));
h = Sentences::Headings::heading_of(position_P);
h = Headings::heading_of(position_P);
@ The pseudo-heading has no list of contents because all objects are created in
source files, each certainly underneath a File (0) heading, so nothing should
@ -910,7 +359,7 @@ and produce an internal error if not.
LOG("%d tags created, %d in ordering\n", no_tags_attached, c);
Sentences::Headings::log_all_headings();
LOG("Making fresh tree:\n");
Sentences::Headings::make_tree();
Headings::make_tree();
Sentences::Headings::log_all_headings();
internal_error_tree_unsafe("reordering of nametags failed");
}

View file

@ -30,17 +30,6 @@ void StructuralSentences::annotate_new_sentence(parse_node *new) {
@
@d NEW_HEADING_HANDLER StructuralSentences::new_heading
=
int StructuralSentences::new_heading(parse_node_tree *T, parse_node *new) {
heading *h = Sentences::Headings::declare(T, new);
ParseTree::set_embodying_heading(new, h);
return Sentences::Headings::include_material(h);
}
@
@d NEW_BEGINEND_HANDLER StructuralSentences::new_beginend
=
@ -58,7 +47,17 @@ void StructuralSentences::new_beginend(parse_node *new, inbuild_copy *C) {
=
void StructuralSentences::new_language(wording W) {
Problems::Issue::sentence_problem(Task::syntax_tree(), _p_(PM_UseElementWithdrawn),
parse_node_tree *T = NULL;
inform_project *project = ProjectBundleManager::from_copy(sfsm_copy);
if (project == NULL) project = ProjectFileManager::from_copy(sfsm_copy);
if (project) T = project->syntax_tree;
inform_extension *ext = ExtensionManager::from_copy(sfsm_copy);
if (ext) T = ext->syntax_tree;
if (T == NULL) internal_error("unable to locate syntax tree");
Problems::Issue::sentence_problem(T, _p_(PM_UseElementWithdrawn),
"the ability to activate or deactivate compiler elements in source text has been withdrawn",
"in favour of a new system with Inform kits.");
}

View file

@ -443,7 +443,7 @@ void Extensions::Files::document_in_detail(OUTPUT_STREAM, inform_extension *E) {
if ((Wordings::first_wn(q->name) >= 0) &&
(NonlocalVariables::is_global(q)) &&
(Lexer::file_of_origin(Wordings::first_wn(q->name)) == E->read_into_file) &&
(Sentences::Headings::indexed(Sentences::Headings::of_wording(q->name)))) {
(Headings::indexed(Headings::of_wording(q->name)))) {
if (<value-understood-variable-name>(q->name) == FALSE)
kc = Extensions::Files::document_headword(OUT,
kc, E, "Values that vary", I"value", q->name);

View file

@ -1365,11 +1365,11 @@ int PL::Actions::index(OUTPUT_STREAM, action_name *an, int pass,
inform_extension **ext, heading **current_area, int f, int *new_par, int bold,
int on_details_page) {
if (an->use_verb_routine_in_I6_library) return f;
heading *definition_area = Sentences::Headings::of_wording(an->present_name);
heading *definition_area = Headings::of_wording(an->present_name);
*new_par = FALSE;
if (pass == 1) {
inform_extension *this_extension =
Sentences::Headings::get_extension_containing(definition_area);
Headings::get_extension_containing(definition_area);
if (*ext != this_extension) {
*ext = this_extension;
if (*ext == NULL) {
@ -1395,7 +1395,7 @@ int PL::Actions::index(OUTPUT_STREAM, action_name *an, int pass,
if ((definition_area != *current_area) && (Extensions::is_standard(*ext))) {
if (f) HTML_CLOSE("p");
HTML_OPEN("p");
wording W = Sentences::Headings::get_text(definition_area);
wording W = Headings::get_text(definition_area);
if (Wordings::nonempty(W)) {
Phrases::Index::index_definition_area(OUT, W, TRUE);
} else if (*ext == NULL) {

View file

@ -108,7 +108,7 @@ void Basics::linguistics_problem_handler(int err_no, wording W, void *ref, int k
@d PREFORM_LANGUAGE_TYPE void
@d PREFORM_ADAPTIVE_PERSON Basics::person
@d EXTENSION_FILE_TYPE void
@d COPY_FILE_TYPE void
=
int Basics::person(void *X) {

View file

@ -109,7 +109,7 @@ void Basics::linguistics_problem_handler(int err_no, wording W, void *ref, int k
@d PREFORM_LANGUAGE_TYPE void
@d PREFORM_ADAPTIVE_PERSON Basics::person
@d EXTENSION_FILE_TYPE void
@d COPY_FILE_TYPE void
=
int Basics::person(void *X) {

View file

@ -101,12 +101,12 @@ which they differ.
source_file *pos = NULL;
Problems::Buffer::clear();
if (problem_count > 0) WRITE_TO(PBUFF, ">---> ");
WRITE_TO(PBUFF, "In<b>");
WRITE_TO(PBUFF, "In");
for (f=FALSE; i<NO_HEADING_LEVELS; i++)
if (problem_headings[i] != NULL) {
wording W = ParseTree::get_text(problem_headings[i]);
#ifdef IF_MODULE
W = Sentences::Headings::get_text(ParseTree::get_embodying_heading(problem_headings[i]));
W = Headings::get_text(ParseTree::get_embodying_heading(problem_headings[i]));
#endif
pos = Lexer::file_of_origin(Wordings::first_wn(W));
if (f) WRITE_TO(PBUFF, ", ");
@ -118,10 +118,10 @@ which they differ.
if (pos) {
#ifdef INBUILD_MODULE
inform_extension *E = Extensions::corresponding_to(pos);
if (E) WRITE_TO(PBUFF, "</b> in the extension <b>%X", E->as_copy->edition->work);
if (E) WRITE_TO(PBUFF, " in the extension %X", E->as_copy->edition->work);
#endif
}
WRITE_TO(PBUFF, "</b>:");
WRITE_TO(PBUFF, ":");
Problems::Buffer::output_problem_buffer(0);
Problems::Buffer::clear();

View file

@ -108,7 +108,7 @@ void Basics::syntax_problem_handler(int err_no, wording W, void *ref, int k) {
@
@d PREFORM_LANGUAGE_TYPE void
@d EXTENSION_FILE_TYPE void
@d COPY_FILE_TYPE void
@ =

View file

@ -54,7 +54,7 @@ int sfsm_inside_rule_mode = FALSE;
int sfsm_skipping_material_at_level = -1;
int sfsm_in_tabbed_mode = FALSE;
int sfsm_main_source_start_wn = -1;
EXTENSION_FILE_TYPE *sfsm_extension = NULL;
COPY_FILE_TYPE *sfsm_copy = NULL;
@ Now for the routine itself. We break into bite-sized chunks, each of which is
despatched to the |Sentences::make_node| routine with a note of the punctuation
@ -63,7 +63,7 @@ finite state machine.
=
void Sentences::break(parse_node_tree *T, wording W, int is_extension,
EXTENSION_FILE_TYPE *from_extension, int bwc) {
COPY_FILE_TYPE *from_copy, int bwc) {
while (((Wordings::nonempty(W))) && (compare_word(Wordings::first_wn(W), PARBREAK_V)))
W = Wordings::trim_first_word(W);
if (Wordings::empty(W)) return;
@ -111,7 +111,7 @@ that is why these are global variables rather than locals in |Sentences::break|.
sfsm_source_file = NULL;
sfsm_inside_rule_mode = FALSE;
sfsm_skipping_material_at_level = -1;
sfsm_extension = from_extension;
sfsm_copy = from_copy;
if (is_extension) sfsm_extension_position = 1;
else sfsm_extension_position = 0;
sfsm_main_source_start_wn = bwc;
@ -202,19 +202,19 @@ sentence divisions. The other cases are more complicated: see below.
stop_character, no_stop_words, sentence_start, position);
@<Issue problem for colon at end of paragraph@> =
SYNTAX_PROBLEM_HANDLER(ParaEndsInColon_SYNERROR, Wordings::new(sentence_start, at-1), sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(ParaEndsInColon_SYNERROR, Wordings::new(sentence_start, at-1), sfsm_copy, 0);
@<Issue problem for colon at end of sentence@> =
SYNTAX_PROBLEM_HANDLER(SentenceEndsInColon_SYNERROR, Wordings::new(sentence_start, at), sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(SentenceEndsInColon_SYNERROR, Wordings::new(sentence_start, at), sfsm_copy, 0);
@<Issue problem for semicolon at end of sentence@> =
SYNTAX_PROBLEM_HANDLER(SentenceEndsInSemicolon_SYNERROR, Wordings::new(sentence_start, at), sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(SentenceEndsInSemicolon_SYNERROR, Wordings::new(sentence_start, at), sfsm_copy, 0);
@<Issue problem for semicolon after colon@> =
SYNTAX_PROBLEM_HANDLER(SemicolonAfterColon_SYNERROR, Wordings::new(sentence_start, at), sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(SemicolonAfterColon_SYNERROR, Wordings::new(sentence_start, at), sfsm_copy, 0);
@<Issue problem for semicolon after full stop@> =
SYNTAX_PROBLEM_HANDLER(SemicolonAfterStop_SYNERROR, Wordings::new(sentence_start, at), sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(SemicolonAfterStop_SYNERROR, Wordings::new(sentence_start, at), sfsm_copy, 0);
@ Colons are normally dividers, too, but an exception is made if they come
between two apparently numerical constructions, because this suggests that
@ -334,7 +334,7 @@ is declared as if it were a super-heading in the text.
@<Reject if we have run on past the end of an extension@> =
if ((sfsm_extension_position == 3) && (begins_or_ends == 0)) {
SYNTAX_PROBLEM_HANDLER(ExtSpuriouslyContinues_SYNERROR, W, sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(ExtSpuriouslyContinues_SYNERROR, W, sfsm_copy, 0);
sfsm_extension_position = 4; /* to avoid multiply issuing this */
}
@ -369,7 +369,7 @@ continuing regardless.
LOOP_THROUGH_WORDING(k, W)
if (k > Wordings::first_wn(W))
if ((Lexer::break_before(k) == '\n') || (Lexer::indentation_level(k) > 0)) {
SYNTAX_PROBLEM_HANDLER(HeadingOverLine_SYNERROR, W, sfsm_extension, k);
SYNTAX_PROBLEM_HANDLER(HeadingOverLine_SYNERROR, W, sfsm_copy, k);
break;
}
@ -386,7 +386,7 @@ newlines automatically added at the end of the feed of any source file.
for (k = Wordings::last_wn(W)+1;
(k<=Wordings::last_wn(W)+8) && (k<lexer_wordcount) && (Lexer::break_before(k) != '\n');
k++) ;
SYNTAX_PROBLEM_HANDLER(HeadingStopsBeforeEndOfLine_SYNERROR, W, sfsm_extension, k);
SYNTAX_PROBLEM_HANDLER(HeadingStopsBeforeEndOfLine_SYNERROR, W, sfsm_copy, k);
}
@ We now have a genuine heading, and can declare it, calling a routine
@ -408,8 +408,8 @@ from an extension, we need to make sure we saw both beginning and end:
@<Issue a problem message if we are missing the begin and end here sentences@> =
switch (sfsm_extension_position) {
case 1: SYNTAX_PROBLEM_HANDLER(ExtNoBeginsHere_SYNERROR, W, sfsm_extension, 0); break;
case 2: SYNTAX_PROBLEM_HANDLER(ExtNoEndsHere_SYNERROR, W, sfsm_extension, 0); break;
case 1: SYNTAX_PROBLEM_HANDLER(ExtNoBeginsHere_SYNERROR, W, sfsm_copy, 0); break;
case 2: SYNTAX_PROBLEM_HANDLER(ExtNoEndsHere_SYNERROR, W, sfsm_copy, 0); break;
case 3: break;
}
@ -465,7 +465,7 @@ sentences and options-file sentences may have been read already.)
if (sfsm_inside_rule_mode)
@<Convert to a COMMAND node and exit rule mode unless a semicolon implies further commands@>
else if (stop_character == ';') {
SYNTAX_PROBLEM_HANDLER(UnexpectedSemicolon_SYNERROR, W, sfsm_extension, 0);
SYNTAX_PROBLEM_HANDLER(UnexpectedSemicolon_SYNERROR, W, sfsm_copy, 0);
stop_character = '.';
}
@ -636,7 +636,7 @@ semicolon to appear indicates an end of the rule.
ParseTree::set_type(new, BEGINHERE_NT);
ParseTree::set_text(new, Wordings::trim_last_word(Wordings::trim_last_word(W)));
#ifdef NEW_BEGINEND_HANDLER
NEW_BEGINEND_HANDLER(new, sfsm_extension);
NEW_BEGINEND_HANDLER(new, sfsm_copy);
#endif
return;
}
@ -644,7 +644,7 @@ semicolon to appear indicates an end of the rule.
ParseTree::set_type(new, ENDHERE_NT);
ParseTree::set_text(new, Wordings::trim_last_word(Wordings::trim_last_word(W)));
#ifdef NEW_BEGINEND_HANDLER
NEW_BEGINEND_HANDLER(new, sfsm_extension);
NEW_BEGINEND_HANDLER(new, sfsm_copy);
#endif
return;
}

View file

@ -108,7 +108,7 @@ void Basics::syntax_problem_handler(int err_no, wording W, void *ref, int k) {
@
@d PREFORM_LANGUAGE_TYPE void
@d EXTENSION_FILE_TYPE void
@d COPY_FILE_TYPE void
@ =