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:
parent
efbc329e7c
commit
3fef66aaf0
|
@ -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@> =
|
||||
;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
565
inbuild/inbuild-module/Chapter 6/Headings.w
Normal file
565
inbuild/inbuild-module/Chapter 6/Headings.w
Normal 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));
|
||||
}
|
|
@ -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@> =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -20,3 +20,5 @@ PM_ExtSpuriouslyContinues
|
|||
PM_ExtMultipleEndsHere
|
||||
PM_ExtMultipleBeginsHere
|
||||
PM_ExtBeginsAfterEndsHere
|
||||
PM_UnknownLanguageElement
|
||||
PM_UnknownVirtualMachine
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
@ =
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
@ =
|
||||
|
||||
|
|
Loading…
Reference in a new issue