diff --git a/docs/core-module/1-wtc.html b/docs/core-module/1-wtc.html index f53d670a7..ea0f2d3e2 100644 --- a/docs/core-module/1-wtc.html +++ b/docs/core-module/1-wtc.html @@ -213,7 +213,7 @@ rough stages. Twenty is plenty. return inform7_task->project->as_copy->edition; } -parse_node_tree *Task::syntax_tree(void) { +parse_node_tree *Task::syntax_tree(void) { return latest_syntax_tree; } diff --git a/docs/core-module/10-aots.html b/docs/core-module/10-aots.html index 30f50ad63..fb65e6a26 100644 --- a/docs/core-module/10-aots.html +++ b/docs/core-module/10-aots.html @@ -330,7 +330,7 @@ context. preform_lookahead_mode = plm; Write the newly discovered specification to the cache for future use13.2; - VerifyTree::verify_structure(spec); + VerifyTree::verify_structure_from(spec); return spec; } diff --git a/docs/core-module/10-varc.html b/docs/core-module/10-varc.html index 111e86cf2..4bf11387b 100644 --- a/docs/core-module/10-varc.html +++ b/docs/core-module/10-varc.html @@ -424,7 +424,7 @@ object. one->next_alternative = NULL; parse_node *new_poss = ExParser::Subtrees::to_specification(SV_not_SN, W, one, B); if (!(Node::is(new_poss, UNKNOWN_NT))) - amb = Node::add_possible_reading(amb, new_poss, W); + amb = SyntaxTree::add_reading(amb, new_poss, W); } if (amb == NULL) amb = Specifications::new_UNKNOWN(W); return amb; @@ -437,7 +437,7 @@ object. hmm->down->next_alternative = NULL; parse_node *new_poss = ExParser::Subtrees::to_specification(SV_not_SN, W, A, hmm); if (!(Node::is(new_poss, UNKNOWN_NT))) - amb = Node::add_possible_reading(amb, new_poss, W); + amb = SyntaxTree::add_reading(amb, new_poss, W); } if (amb == NULL) amb = Specifications::new_UNKNOWN(W); return amb; diff --git a/docs/core-module/14-cn.html b/docs/core-module/14-cn.html index d709487d5..7410ea154 100644 --- a/docs/core-module/14-cn.html +++ b/docs/core-module/14-cn.html @@ -270,7 +270,7 @@ created condition: reading->next_alternative = NULL; reading = Conditions::attach_historic_requirement(reading, tp); if (Node::is(reading, UNKNOWN_NT) == FALSE) - amb = Node::add_possible_reading(amb, + amb = SyntaxTree::add_reading(amb, reading, Node::get_text(cond)); } return amb; diff --git a/docs/core-module/14-ds2.html b/docs/core-module/14-ds2.html index 1fec8fbca..61e1cf23f 100644 --- a/docs/core-module/14-ds2.html +++ b/docs/core-module/14-ds2.html @@ -4331,6 +4331,36 @@ at run time. } } +

§29.

+ +
define AMBIGUITY_JOIN_SYNTAX_CALLBACK Dash::ambiguity_join
+
+
+parse_node *Dash::ambiguity_join(parse_node *existing, parse_node *reading) {
+    if ((ParseTreeUsage::is_phrasal(reading)) &&
+        (Node::get_type(reading) == Node::get_type(existing))) {
+        Dash::add_pr_inv(existing, reading);
+        return existing;
+    }
+    return NULL;
+}
+
+void Dash::add_pr_inv(parse_node *E, parse_node *reading) {
+    for (parse_node *N = reading->down->down, *next_N = (N)?(N->next_alternative):NULL; N;
+        N = next_N, next_N = (N)?(N->next_alternative):NULL)
+        Dash::add_single_pr_inv(E, N);
+}
+
+void Dash::add_single_pr_inv(parse_node *E, parse_node *N) {
+    E = E->down->down;
+    if (Invocations::eq(E, N)) return;
+    while ((E) && (E->next_alternative)) {
+        E = E->next_alternative;
+        if (Invocations::eq(E, N)) return;
+    }
+    E->next_alternative = N; N->next_alternative = NULL;
+}
+
diff --git a/docs/core-module/25-cp.html b/docs/core-module/25-cp.html index 7d31ed1be..e4c113e17 100644 --- a/docs/core-module/25-cp.html +++ b/docs/core-module/25-cp.html @@ -160,9 +160,9 @@ should always be supplied for "To..." phrases, but left null for rules. current_sentence = ph->declaration_node; if (Phrases::Context::compile_test_head(ph, acl) == FALSE) { if (ph->declaration_node) { - VerifyTree::verify_structure(ph->declaration_node); + VerifyTree::verify_structure_from(ph->declaration_node); Routines::Compile::code_block_outer(1, ph->declaration_node->down); - VerifyTree::verify_structure(ph->declaration_node); + VerifyTree::verify_structure_from(ph->declaration_node); } current_sentence = ph->declaration_node; Phrases::Context::compile_test_tail(ph, acl); diff --git a/docs/core-module/25-in.html b/docs/core-module/25-in.html index e7ae61e9d..6cf1fd6b0 100644 --- a/docs/core-module/25-in.html +++ b/docs/core-module/25-in.html @@ -649,7 +649,7 @@ Inform prioritises phrases.

-int Invocations::eq(parse_node *inv1, parse_node *inv2) {
+int Invocations::eq(parse_node *inv1, parse_node *inv2) {
     if ((inv1) && (inv2 == NULL)) return FALSE;
     if ((inv1 == NULL) && (inv2)) return FALSE;
     if (inv1 == NULL) return TRUE;
@@ -682,7 +682,7 @@ in a list:
 

-void Invocations::log_list(parse_node *invl) {
+void Invocations::log_list(parse_node *invl) {
     parse_node *inv;
     LOG("Invocation list (%d):\n", Invocations::length_of_list(invl));
     int n = 0;
diff --git a/docs/core-module/6-nv.html b/docs/core-module/6-nv.html
index c01582c56..9b4e0799e 100644
--- a/docs/core-module/6-nv.html
+++ b/docs/core-module/6-nv.html
@@ -1408,6 +1408,16 @@ usages to the debugging log.
         }
 }
 
+

§26.

+ +
define TRACING_LINGUISTICS_CALLBACK NewVerbs::trace_parsing
+
+
+int NewVerbs::trace_parsing(void) {
+    if (SyntaxTree::is_trace_set(Task::syntax_tree())) return TRUE;
+    return FALSE;
+}
+
diff --git a/docs/core-module/7-ns.html b/docs/core-module/7-ns.html index 75e17405c..c000dc3b4 100644 --- a/docs/core-module/7-ns.html +++ b/docs/core-module/7-ns.html @@ -176,8 +176,8 @@ in quick succession, the second run-through does nothing.) } void Sentences::VPs::visit(parse_node *p) { if (Node::get_type(p) == TRACE_NT) { - trace_sentences = 1 - trace_sentences; - Log::tracing_on(trace_sentences, I"Diagramming"); + SyntaxTree::toggle_trace(Task::syntax_tree()); + Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), I"Diagramming"); } if ((Node::get_type(p) == SENTENCE_NT) && (Annotations::read_int(p, sentence_unparsed_ANNOT))) { @@ -443,7 +443,7 @@ action declarations continue with usually extensive further text: Annotations::write_int(VP_PN, verb_id_ANNOT, ASSERT_VB); SyntaxTree::graft(Task::syntax_tree(), VP_PN, nss_tree_head); - if (trace_sentences) { + if (SyntaxTree::is_trace_set(Task::syntax_tree())) { LOG("$T\n", nss_tree_head); STREAM_FLUSH(DL); } *X = 0; diff --git a/docs/core-module/7-oaf.html b/docs/core-module/7-oaf.html index d952291e7..c2201ea54 100644 --- a/docs/core-module/7-oaf.html +++ b/docs/core-module/7-oaf.html @@ -131,7 +131,7 @@ not be treated as a possessive, and expunge them. } void Sentences::Rearrangement::tidy_up_ofs_and_froms(void) { - VerifyTree::verify_integrity(Task::syntax_tree()->root_node, FALSE); + VerifyTree::verify_integrity(Task::syntax_tree()); SyntaxTree::traverse_wfirst(Task::syntax_tree(), Sentences::Rearrangement::traverse_for_property_names); SyntaxTree::traverse(Task::syntax_tree(), Sentences::Rearrangement::traverse_for_nonbreaking_ofs); } diff --git a/docs/core-module/7-ptu.html b/docs/core-module/7-ptu.html index 283d50dfe..be2f6bcd7 100644 --- a/docs/core-module/7-ptu.html +++ b/docs/core-module/7-ptu.html @@ -212,28 +212,28 @@ also makes it easier for us to manipulate the results.
 void ParseTreeUsage::write_parentage_permissions(void) {
-    parentage_allowed[L2_NCAT][L3_NCAT] = TRUE;
-    parentage_allowed[L3_NCAT][L3_NCAT] = TRUE;
-    parentage_allowed[L2_NCAT][L4_NCAT] = TRUE;
-    parentage_allowed[L4_NCAT][L4_NCAT] = TRUE;
-    parentage_allowed[L4_NCAT][UNKNOWN_NCAT] = TRUE;
+    NodeType::allow_parentage_for_categories(L2_NCAT, L3_NCAT);
+    NodeType::allow_parentage_for_categories(L3_NCAT, L3_NCAT);
+    NodeType::allow_parentage_for_categories(L2_NCAT, L4_NCAT);
+    NodeType::allow_parentage_for_categories(L4_NCAT, L4_NCAT);
+    NodeType::allow_parentage_for_categories(L4_NCAT, UNKNOWN_NCAT);
 
-    parentage_allowed[L4_NCAT][LVALUE_NCAT] = TRUE;
-    parentage_allowed[L4_NCAT][RVALUE_NCAT] = TRUE;
-    parentage_allowed[L4_NCAT][COND_NCAT] = TRUE;
+    NodeType::allow_parentage_for_categories(L4_NCAT, LVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(L4_NCAT, RVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(L4_NCAT, COND_NCAT);
 
-    parentage_allowed[LVALUE_NCAT][UNKNOWN_NCAT] = TRUE;
-    parentage_allowed[RVALUE_NCAT][UNKNOWN_NCAT] = TRUE;
-    parentage_allowed[COND_NCAT][UNKNOWN_NCAT] = TRUE;
-    parentage_allowed[LVALUE_NCAT][LVALUE_NCAT] = TRUE;
-    parentage_allowed[RVALUE_NCAT][LVALUE_NCAT] = TRUE;
-    parentage_allowed[COND_NCAT][LVALUE_NCAT] = TRUE;
-    parentage_allowed[LVALUE_NCAT][RVALUE_NCAT] = TRUE;
-    parentage_allowed[RVALUE_NCAT][RVALUE_NCAT] = TRUE;
-    parentage_allowed[COND_NCAT][RVALUE_NCAT] = TRUE;
-    parentage_allowed[LVALUE_NCAT][COND_NCAT] = TRUE;
-    parentage_allowed[RVALUE_NCAT][COND_NCAT] = TRUE;
-    parentage_allowed[COND_NCAT][COND_NCAT] = TRUE;
+    NodeType::allow_parentage_for_categories(LVALUE_NCAT, UNKNOWN_NCAT);
+    NodeType::allow_parentage_for_categories(RVALUE_NCAT, UNKNOWN_NCAT);
+    NodeType::allow_parentage_for_categories(COND_NCAT, UNKNOWN_NCAT);
+    NodeType::allow_parentage_for_categories(LVALUE_NCAT, LVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(RVALUE_NCAT, LVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(COND_NCAT, LVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(LVALUE_NCAT, RVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(RVALUE_NCAT, RVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(COND_NCAT, RVALUE_NCAT);
+    NodeType::allow_parentage_for_categories(LVALUE_NCAT, COND_NCAT);
+    NodeType::allow_parentage_for_categories(RVALUE_NCAT, COND_NCAT);
+    NodeType::allow_parentage_for_categories(COND_NCAT, COND_NCAT);
 }
 

§10.

@@ -406,7 +406,7 @@ also makes it easier for us to manipulate the results.

define IMMUTABLE_NODE ParseTreeUsage::immutable
-define SENTENCE_NODE_SYNTAX_CALLBACK ParseTreeUsage::second_level
+define IS_SENTENCE_NODE_SYNTAX_CALLBACK ParseTreeUsage::second_level
 
 int ParseTreeUsage::second_level(node_type_t t) {
@@ -456,7 +456,7 @@ also makes it easier for us to manipulate the results.
     return FALSE;
 }
 
-int ParseTreeUsage::is_phrasal(parse_node *pn) {
+int ParseTreeUsage::is_phrasal(parse_node *pn) {
     if (NodeType::has_flag(Node::get_type(pn), PHRASAL_NFLAG)) return TRUE;
     return FALSE;
 }
@@ -470,7 +470,7 @@ be such that their head nodes each pass this test:
 
 
 int ParseTreeUsage::allow_in_assertions(parse_node *p) {
-    VerifyTree::verify_structure(p);
+    VerifyTree::verify_structure_from(p);
     if (NodeType::has_flag(Node::get_type(p), ASSERT_NFLAG)) return TRUE;
     return FALSE;
 }
@@ -565,7 +565,8 @@ be such that their head nodes each pass this test:
 
 
 void ParseTreeUsage::verify(void) {
-    VerifyTree::verify_node(Task::syntax_tree());
+    VerifyTree::verify_integrity(Task::syntax_tree());
+    VerifyTree::verify_structure(Task::syntax_tree());
 }
 

§16.

diff --git a/docs/core-module/9-tbath.html b/docs/core-module/9-tbath.html index 01c200ca3..b3a4cc6fe 100644 --- a/docs/core-module/9-tbath.html +++ b/docs/core-module/9-tbath.html @@ -289,7 +289,7 @@ the massive Assertions::Maker if (Assertions::Creator::consult_the_creator(px, py) == FALSE) return; } - if (trace_sentences) LOG("$T", current_sentence); + if (SyntaxTree::is_trace_set(Task::syntax_tree())) LOG("$T", current_sentence); if (<s-existential-np>(Node::get_text(px))) { if (traverse == 1) Assertions::Copular::make_existential_assertion(py); px = py; diff --git a/docs/core-module/9-tfa.html b/docs/core-module/9-tfa.html index cf583848a..5d9834142 100644 --- a/docs/core-module/9-tfa.html +++ b/docs/core-module/9-tfa.html @@ -188,7 +188,7 @@ we don't bother to print details of the closing void Assertions::Traverse::traverse(int pass) { Assertions::Traverse::new_discussion(); clear memory of what the subject and object of discussion are traverse = pass; - trace_sentences = FALSE; + SyntaxTree::clear_trace(Task::syntax_tree()); if (sentence_handlers_initialised == FALSE) Initialise sentence handlers7.3; @@ -308,7 +308,7 @@ handlers until right at the end of the program. The routine which does so,

-    if ((trace_sentences) && (Node::get_type(p) != TRACE_NT))
+    if ((SyntaxTree::is_trace_set(Task::syntax_tree())) && (Node::get_type(p) != TRACE_NT))
         LOG("\n[%W]\n", Node::get_text(p));
 
     If this sentence can be handled, then do so and continue7.2.2.1;
@@ -362,9 +362,9 @@ for the telemetry file.
             "so I'll note it down in the Telemetry file (if you're keeping one.)");
          telemetry_recording = tr;
     } else {
-        trace_sentences = 1 - trace_sentences;
-        if (traverse == 1) Log::tracing_on(trace_sentences, I"Pass 1");
-        else Log::tracing_on(trace_sentences, I"Pass 2");
+        SyntaxTree::toggle_trace(Task::syntax_tree());
+        if (traverse == 1) Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), I"Pass 1");
+        else Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), I"Pass 2");
     }
 }
 
diff --git a/docs/linguistics-module/5-vp.html b/docs/linguistics-module/5-vp.html index 858a5cfd6..362a8cc61 100644 --- a/docs/linguistics-module/5-vp.html +++ b/docs/linguistics-module/5-vp.html @@ -194,9 +194,9 @@ verbs were added to the grammar. Still, it was a pity.
 <sentence> internal {
-    if (trace_sentences) { LOG("Parsing the sentence: %W\n", W); LOG_INDENT; }
+    if (VerbPhrases::tracing()) { LOG("Parsing the sentence: %W\n", W); LOG_INDENT; }
     int rv = VerbPhrases::seek(W, X, XP, 0);
-    if (trace_sentences) {
+    if (VerbPhrases::tracing()) {
         LOG_OUTDENT;
         if (rv) {
             LOG("Passed\n"); LOG_INDENT;
@@ -234,7 +234,7 @@ which word positions might be the beginning of verb phrases.
 int VerbPhrases::seek(wording W, int *X, void **XP, int existential_OP_edge) {
     int viable[VIABILITY_MAP_SIZE];
     Calculate the viability map7.1;
-    if (trace_sentences) Log the viability map7.2;
+    if (VerbPhrases::tracing()) Log the viability map7.2;
     Seek verb usages7.3;
     return FALSE;
 }
@@ -478,7 +478,7 @@ certainty are removed from these.
     }
     certainty = pre_certainty;
     if (certainty == UNKNOWN_CE) certainty = post_certainty;
-    if (trace_sentences) LOG("Found usage, pass %d tier %d: (%W) $w (%W)\n",
+    if (VerbPhrases::tracing()) LOG("Found usage, pass %d tier %d: (%W) $w (%W)\n",
         pass, tier->priority, ISW, vi, IOW);
 
@@ -506,7 +506,8 @@ who is in the Dining Room" (note the additional "is"), it would. if (existential_OP_edge > 0) { i.e., if we are looking for "(S which) verbs (O)" if (<pre-verb-rc-marker>(SW)) { there is indeed a "which" at the end of SW SW = GET_RW(<pre-verb-rc-marker>, 1); so trim it off - if (trace_sentences) LOG("Trimmed to: (%W) $w (%W)\n", SW, vi, OW); + if (VerbPhrases::tracing()) + LOG("Trimmed to: (%W) $w (%W)\n", SW, vi, OW); } } @@ -555,7 +556,7 @@ who is in the Dining Room" (note the additional "is"), it would. usage_succeeds = TRUE; } if (usage_succeeds == FALSE) { - if (trace_sentences) LOG("$w + $p + $p : failed for lack of $p\n", + if (VerbPhrases::tracing()) LOG("$w + $p + $p : failed for lack of $p\n", vi, prep, second_prep, prep); continue; } @@ -578,7 +579,7 @@ who is in the Dining Room" (note the additional "is"), it would. usage_succeeds = TRUE; } if (usage_succeeds == FALSE) { - if (trace_sentences) LOG("$w + $p + $p : failed for lack of $p\n", + if (VerbPhrases::tracing()) LOG("$w + $p + $p : failed for lack of $p\n", vi, prep, second_prep, second_prep); continue; } @@ -614,10 +615,12 @@ representing the verb. VP_PN = VerbPhrases::accept(vf, VP_PN, SW, OW, O2W); if (VP_PN) { *XP = VP_PN; - if (trace_sentences) LOG("Accepted as $w + $p + $p\n", vi, prep, second_prep); + if (VerbPhrases::tracing()) + LOG("Accepted as $w + $p + $p\n", vi, prep, second_prep); return TRUE; } else { - if (trace_sentences) LOG("Rejected as $w + $p + $p\n", vi, prep, second_prep); + if (VerbPhrases::tracing()) + LOG("Rejected as $w + $p + $p\n", vi, prep, second_prep); }
@@ -724,6 +727,18 @@ the exactly equivalent idea of the hat being worn by Darcy. }
+

§10.

+ +
+int VerbPhrases::tracing(void) {
+    #ifdef TRACING_LINGUISTICS_CALLBACK
+    return TRACING_LINGUISTICS_CALLBACK();
+    #endif
+    #ifndef TRACING_LINGUISTICS_CALLBACK
+    return FALSE;
+    #endif
+}
+
diff --git a/docs/linguistics-test/1-pc.html b/docs/linguistics-test/1-pc.html index 05bd4cc16..3b774f7ac 100644 --- a/docs/linguistics-test/1-pc.html +++ b/docs/linguistics-test/1-pc.html @@ -60,8 +60,6 @@ SyntaxModule::start(); LinguisticsModule::start(); - Unit::start_diagrams(); - CommandLine::declare_heading(L"linguistics-test: a tool for testing the linguistics module\n"); CommandLine::declare_switch(TEST_DIAGRAMS_CLSW, L"test-diagrams", 2, @@ -79,7 +77,7 @@ void Main::respond(int id, int val, text_stream *arg, void *state) { switch (id) { - case TEST_DIAGRAMS_CLSW: Main::load(I"Syntax.preform"); Unit::test_diagrams(arg); break; + case TEST_DIAGRAMS_CLSW: Main::load(I"Syntax.preform"); Unit::test_diagrams(arg); break; } } diff --git a/docs/linguistics-test/1-ut.html b/docs/linguistics-test/1-ut.html index 14f7476eb..411547322 100644 --- a/docs/linguistics-test/1-ut.html +++ b/docs/linguistics-test/1-ut.html @@ -52,7 +52,7 @@ function togglePopup(material_id) {

How we shall test it.

-
+

§1.

@@ -75,24 +75,33 @@ function togglePopup(material_id) { return TRUE; } -

§3.

+

§3. Minimal Preform grammar. Only <dividing-sentence> can ever match, since the others are wired to match +any text but then fail. +

 <dividing-sentence> ::=
-    chapter ... |    ==> 1
-    section ...				==> 2
+    chapter ... |  ==> 1
+    section ...    ==> 2
 
 <structural-sentence> ::=
-    ...						==> TRUE; return FAIL_NONTERMINAL;
+    ... ==> TRUE; return FAIL_NONTERMINAL;
 
 <language-modifying-sentence> ::=
-    ...						==> TRUE; return FAIL_NONTERMINAL;
+    ... ==> TRUE; return FAIL_NONTERMINAL;
 
-<unexceptional-sentence> ::=
-    <sentence>				==> Report any error3.1
+<comma-divisible-sentence> ::=
+    ... ==> TRUE; return FAIL_NONTERMINAL;
 
-

§3.1. Report any error3.1 = +

§4.

+ +
+<unexceptional-sentence> ::=
+    <sentence>				==> Report any error4.1
+
+ +

§4.1. Report any error4.1 =

@@ -101,25 +110,21 @@ function togglePopup(material_id) {
         Errors::nowhere("two certainties");
     *XP = VP_PN;
 
- -

§4.

+ +

§5.

 <stock> ::=
     verb <cardinal-number> ...	==> R[1]; *XP = Conjugation::conjugate(WordAssemblages::from_wording(FW[1]), English_language);
 
-

§5. Syntax tree.

+

§6. Syntax tree.

 int my_first_verb = TRUE;
 
-void Unit::start_diagrams(void) {
-    trace_sentences = TRUE;
-}
-
 parse_node_tree *syntax_tree = NULL;
-void Unit::test_diagrams(text_stream *arg) {
+void Unit::test_diagrams(text_stream *arg) {
     Streams::enable_debugging(STDOUT);
     filename *F = Filenames::from_text(arg);
     feed_t FD = Feeds::begin();
@@ -133,14 +138,15 @@ function togglePopup(material_id) {
     text_stream *save_DL = DL;
     DL = STDOUT;
     Streams::enable_debugging(DL);
-    SyntaxTree::traverse(syntax_tree, Unit::diagram);
-    Node::log_tree(DL, syntax_tree->root_node);
+    SyntaxTree::clear_trace(syntax_tree);
+    SyntaxTree::traverse(syntax_tree, Unit::diagram);
+    Node::log_tree(DL, syntax_tree->root_node);
     DL = save_DL;
 }
 
 void Unit::diagram(parse_node *p) {
-    if (Node::get_type(p) == SENTENCE_NT) {
-        wording W = Node::get_text(p);
+    if (Node::get_type(p) == SENTENCE_NT) {
+        wording W = Node::get_text(p);
         if (<stock>(W)) {
             verb_conjugation *vc = <<rp>>;
             int cop = FALSE;
diff --git a/docs/problems-test/1-pc.html b/docs/problems-test/1-pc.html
index 1c1546f1e..cce19256d 100644
--- a/docs/problems-test/1-pc.html
+++ b/docs/problems-test/1-pc.html
@@ -73,7 +73,7 @@
 
 void Main::respond(int id, int val, text_stream *arg, void *state) {
     switch (id) {
-        case TEST_PROBLEMS_CLSW: Main::load(I"Syntax.preform"); Unit::test_problems(arg); break;
+        case TEST_PROBLEMS_CLSW: Main::load(I"Syntax.preform"); Unit::test_problems(arg); break;
     }
 }
 
diff --git a/docs/problems-test/1-ut.html b/docs/problems-test/1-ut.html
index 7b75fc68c..665715c1e 100644
--- a/docs/problems-test/1-ut.html
+++ b/docs/problems-test/1-ut.html
@@ -52,28 +52,40 @@ function togglePopup(material_id) {
     
 

How we shall test it.

-
+

§1.

-
-parse_node_tree *syntax_tree = NULL;
+
+parse_node_tree *syntax_tree = NULL;
+
+

§2. Minimal Preform grammar. Only <dividing-sentence> can ever match, since the others are wired to match +any text but then fail. +

+
 <dividing-sentence> ::=
-    chapter ... |    ==> 1
-    section ...				==> 2
+    chapter ... |  ==> 1
+    section ...    ==> 2
 
 <structural-sentence> ::=
-    ...						==> TRUE; return FAIL_NONTERMINAL;
+    ... ==> TRUE; return FAIL_NONTERMINAL;
 
 <language-modifying-sentence> ::=
-    ...						==> TRUE; return FAIL_NONTERMINAL;
+    ... ==> TRUE; return FAIL_NONTERMINAL;
 
-<scan-individual-phrase> ::=
-    ... banana ...			==> Issue PM_UnexpectedFruit problem1.1;
+<comma-divisible-sentence> ::=
+    ... ==> TRUE; return FAIL_NONTERMINAL;
 
-

§1.1. Issue PM_UnexpectedFruit problem1.1 = +

§3.

+ +
+<scan-individual-phrase> ::=
+    ... banana ...			==> Issue PM_UnexpectedFruit problem3.1;
+
+ +

§3.1. Issue PM_UnexpectedFruit problem3.1 =

@@ -84,8 +96,8 @@ function togglePopup(material_id) {
         "will be ruined.");
     Problems::issue_problem_end();
 
- -

§2. Syntax tree.

+ +

§4. Syntax tree.

 void Unit::test_problems(text_stream *arg) {
@@ -98,12 +110,12 @@ function togglePopup(material_id) {
     PRINT("Read %d words\n", Wordings::length(W));
     Sentences::break(syntax_tree, W);
 
-    SyntaxTree::traverse(syntax_tree, Unit::scan_tree);
+    SyntaxTree::traverse(syntax_tree, Unit::scan_tree);
 }
 
 void Unit::scan_tree(parse_node *p) {
-    if (Node::get_type(p) == SENTENCE_NT) {
-        wording W = Node::get_text(p);
+    if (Node::get_type(p) == SENTENCE_NT) {
+        wording W = Node::get_text(p);
         <scan-individual-phrase>(W);
     }
 }
diff --git a/docs/supervisor-module/2-ce.html b/docs/supervisor-module/2-ce.html
index 495f73d6f..b2e73bd38 100644
--- a/docs/supervisor-module/2-ce.html
+++ b/docs/supervisor-module/2-ce.html
@@ -111,7 +111,7 @@ fields are blank.
 

-copy_error *CopyErrors::new(int cat, int subcat) {
+copy_error *CopyErrors::new(int cat, int subcat) {
     copy_error *CE = CREATE(copy_error);
     CE->copy = NULL;
     CE->error_category = cat;
@@ -171,7 +171,7 @@ we also offer these functions to tack extra details on:
     CE->details_work2 = w2;
 }
 
-void CopyErrors::supply_node(copy_error *CE, parse_node *n) {
+void CopyErrors::supply_node(copy_error *CE, parse_node *n) {
     CE->details_node = n;
 }
 
diff --git a/docs/supervisor-module/2-cps.html b/docs/supervisor-module/2-cps.html
index 27bb9dc97..ecec7e3f1 100644
--- a/docs/supervisor-module/2-cps.html
+++ b/docs/supervisor-module/2-cps.html
@@ -144,7 +144,7 @@ for later reporting. These are stored in a list.
 

-void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
+void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
     if (C == NULL) internal_error("no copy to attach to");
     CopyErrors::supply_attached_copy(CE, C);
     ADD_TO_LINKED_LIST(CE, copy_error, C->errors_reading_source_text);
diff --git a/docs/supervisor-module/4-em.html b/docs/supervisor-module/4-em.html
index 7b88fe828..6e3959363 100644
--- a/docs/supervisor-module/4-em.html
+++ b/docs/supervisor-module/4-em.html
@@ -113,7 +113,7 @@ which stores data about extensions used by the Inform compiler.
 

-inform_extension *ExtensionManager::from_copy(inbuild_copy *C) {
+inform_extension *ExtensionManager::from_copy(inbuild_copy *C) {
     if ((C) && (C->edition->work->genre == extension_genre)) {
         return RETRIEVE_POINTER_inform_extension(C->metadata);
     }
diff --git a/docs/supervisor-module/6-inc.html b/docs/supervisor-module/6-inc.html
index a027ffc41..df25f6df5 100644
--- a/docs/supervisor-module/6-inc.html
+++ b/docs/supervisor-module/6-inc.html
@@ -415,7 +415,7 @@ use an extension which is marked as not working on the current VM.
 

§9.

-void Inclusions::check_begins_here(parse_node *PN, inform_extension *E) {
+void Inclusions::check_begins_here(parse_node *PN, inform_extension *E) {
     inbuild_copy *S = inclusions_errors_to;
     inclusions_errors_to = E->as_copy;
     <begins-here-sentence-subject>(Node::get_text(PN));
@@ -431,7 +431,7 @@ the "begins here".
 <the-prefix-for-extensions> ::=
     the ...
 
-void Inclusions::check_ends_here(parse_node *PN, inform_extension *E) {
+void Inclusions::check_ends_here(parse_node *PN, inform_extension *E) {
     inbuild_copy *S = inclusions_errors_to;
     inclusions_errors_to = E->as_copy;
     wording W = Node::get_text(PN);
diff --git a/docs/supervisor-module/6-st.html b/docs/supervisor-module/6-st.html
index f5555cd8d..2da64a6e3 100644
--- a/docs/supervisor-module/6-st.html
+++ b/docs/supervisor-module/6-st.html
@@ -219,7 +219,7 @@ finished loading the bulk in, it calls:
 not for inbuild, which isn't in the inventions business.
 

-
define SENTENCE_ANNOTATION_SYNTAX_CALLBACK SourceText::annotate_new_sentence
+
define NEW_NONSTRUCTURAL_SENTENCE_SYNTAX_CALLBACK SourceText::annotate_new_sentence
 
 void SourceText::annotate_new_sentence(parse_node *new) {
@@ -360,7 +360,27 @@ never accidentally match in the main source text.
     include (- ...                                 ==> 0; sfsm->nt = INFORM6CODE_NT;
 
-

§11. Properly speaking, despite the definition above, language modifying sentences +

§11. Rules are ordinarily detected by their colon, which divides the header from the +rest: colons are not otherwise legal in Inform. But there's an exception. If the +sentence consists of text matching the following grammar, followed by comma, +followed by more text, then the comma is read as if it's a colon and the +sentence becomes a rule. For example: +

+ +
+

Instead of going north, try entering the cage

+
+ +
+<comma-divisible-sentence> ::=
+    instead of ... |
+    every turn *** |
+    before ... |
+    after ... |
+    when ...
+
+ +

§12. Properly speaking, despite the definition above, language modifying sentences are nonstructural. So what are they doing here? The answer is that we need to read them early on, because they affect the way that they parse all other sentences. Whereas other nonstructural sentences can wait, these can't. @@ -372,12 +392,12 @@ sentences. Whereas other nonstructural sentences can wait, these can't. use ... language element/elements ==> -1

-

§12. The following callback function is called by syntax when it breaks a +

§13. The following callback function is called by syntax when it breaks a sentence of type BEGINHERE_NT or ENDHERE_NT — i.e., the beginning or end of an extension.

-
define BEGIN_OR_END_HERE_SYNTAX_CALLBACK SourceText::new_beginend
+
define BEGIN_OR_END_HERE_SYNTAX_CALLBACK SourceText::new_beginend
 
 void SourceText::new_beginend(parse_node *pn, inbuild_copy *C) {
@@ -386,7 +406,7 @@ of an extension.
     if (Node::get_type(pn) == ENDHERE_NT) Inclusions::check_ends_here(pn, E);
 }
 
-

§13. Lastly, this callback is called by syntax when it hits a sentence like: +

§14. Lastly, this callback is called by syntax when it hits a sentence like:

@@ -397,7 +417,7 @@ of an extension. stack into the new world of kits), so we issue a syntax error.

-
define LANGUAGE_ELEMENT_SYNTAX_CALLBACK SourceText::new_language
+
define LANGUAGE_ELEMENT_SYNTAX_CALLBACK SourceText::new_language
 
 void SourceText::new_language(wording W) {
diff --git a/docs/syntax-module/1-sm.html b/docs/syntax-module/1-sm.html
index c42986daf..4683f65f3 100644
--- a/docs/syntax-module/1-sm.html
+++ b/docs/syntax-module/1-sm.html
@@ -84,19 +84,19 @@ all we need do is set up some debugging log facilities.
 
 void SyntaxModule::start(void) {
     NodeType::make_parentage_allowed_table();
-    NodeType::metadata_setup();
+    NodeType::metadata_setup();
     Annotations::make_annotation_allowed_table();
-    Writers::register_logger('m', Node::log_tree);    /* |$m| = log syntax tree from node */
-    Writers::register_logger_I('N', NodeType::log);  /* |$N| = log individual node type */
-    Writers::register_logger('P', Node::log_node);    /* |$P| = log individual parse node */
-    Writers::register_logger('T', Node::log_subtree); /* |$T| = log tree under node */
+    Writers::register_logger('m', Node::log_tree);    /* |$m| = log syntax tree from node */
+    Writers::register_logger_I('N', NodeType::log);  /* |$N| = log individual node type */
+    Writers::register_logger('P', Node::log_node);    /* |$P| = log individual parse node */
+    Writers::register_logger('T', Node::log_subtree); /* |$T| = log tree under node */
 }
 
 void SyntaxModule::end(void) {
 }
 
diff --git a/docs/syntax-module/2-na.html b/docs/syntax-module/2-na.html index fddf47d99..4db57bcd0 100644 --- a/docs/syntax-module/2-na.html +++ b/docs/syntax-module/2-na.html @@ -104,7 +104,7 @@ is unannotated.

-void Annotations::clear(parse_node *PN) {
+void Annotations::clear(parse_node *PN) {
     PN->annotations = NULL;
 }
 
@@ -128,7 +128,7 @@ other for reading pointers to objects.

-int Annotations::read_int(parse_node *PN, int id) {
+int Annotations::read_int(parse_node *PN, int id) {
     parse_node_annotation *pna;
     if (PN)
         for (pna=PN->annotations; pna; pna=pna->next_annotation)
@@ -154,7 +154,7 @@ one (on the same node) overwrites it, but this is not an error.
 

-void Annotations::write_int(parse_node *PN, int id, int v) {
+void Annotations::write_int(parse_node *PN, int id, int v) {
     parse_node_annotation *newpna, *pna, *final = NULL;
     if (PN == NULL) internal_error("annotated null PN");
     for (pna=PN->annotations; pna; pna=pna->next_annotation) {
@@ -224,7 +224,7 @@ wants to.
 

-void Annotations::copy(parse_node *to, parse_node *from) {
+void Annotations::copy(parse_node *to, parse_node *from) {
     to->annotations = NULL;
     for (parse_node_annotation *pna = from->annotations, *latest = NULL;
         pna; pna=pna->next_annotation) {
@@ -284,7 +284,7 @@ expected also to call the following:
 }
 void Annotations::allow_for_category(int cat, int annot) {
     LOOP_OVER_ENUMERATED_NTS(t)
-        if (NodeType::category(t) == cat)
+        if (NodeType::category(t) == cat)
             Annotations::allow(t, annot);
 }
 
@@ -306,8 +306,8 @@ is rarely used by Inform, but is needed when a node changes its type.

-void Annotations::clear_invalid(parse_node *pn) {
-    node_type_t nt = Node::get_type(pn);
+void Annotations::clear_invalid(parse_node *pn) {
+    node_type_t nt = Node::get_type(pn);
     while ((pn->annotations) &&
         (!(Annotations::is_allowed(nt, pn->annotations->annotation_id))))
         pn->annotations = pn->annotations->next_annotation;
diff --git a/docs/syntax-module/2-nt.html b/docs/syntax-module/2-nt.html
index 05012c79a..42b6673a1 100644
--- a/docs/syntax-module/2-nt.html
+++ b/docs/syntax-module/2-nt.html
@@ -1,7 +1,7 @@
 
 
 	
-		Node Tyoes
+		Node Types
 
 		
 		
@@ -61,12 +61,12 @@ function togglePopup(material_id) {
 
 		
 		
- + +

Each node in a syntax tree has a type, which informs whether it can have child nodes, and what in general terms it means.

-
+

§1. Node types. Each node has a "node type". Some of those are defined here with *_NT names — these are the "enumerated" node types. But every *_MC code, @@ -88,7 +88,7 @@ enumerated codes all have bit 32 set, and therefore no for (node_type_t t=ENUMERATED_NT_BASE; t<ENUMERATED_NT_BASE+NO_DEFINED_NT_VALUES; t++)

-int NodeType::is_enumerated(node_type_t t) {
+int NodeType::is_enumerated(node_type_t t) {
     if ((t >= ENUMERATED_NT_BASE) &&
         (t < ENUMERATED_NT_BASE+NO_DEFINED_NT_VALUES)) return TRUE;
     return FALSE;
@@ -132,19 +132,34 @@ to be children of which others, thus enforcing this hierarchy.
     for (int i = 0; i < NO_DEFINED_NCAT_VALUES; i++)
         for (int j = 0; j < NO_DEFINED_NCAT_VALUES; j++)
             parentage_allowed[i][j] = FALSE;
-    parentage_allowed[L1_NCAT][L1_NCAT] = TRUE;
+    NodeType::allow_parentage_for_categories(L1_NCAT, L1_NCAT);
     #ifdef PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK
     PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK();
     #endif
+    #ifdef MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK
+    MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK();
+    #endif
+    #ifdef EVEN_MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK
+    EVEN_MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK();
+    #endif
 }
 
-

§5. The bitmap of node flags currently contains only two: +

§5. The callback function PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK should +call this as needed to fill in more permissions: +

+ +
+void NodeType::allow_parentage_for_categories(int A, int B) {
+    parentage_allowed[A][B] = TRUE;
+}
+
+

§6. The bitmap of node flags currently contains only two:

define DONT_VISIT_NFLAG 0x00000001  not visited in traverses
 define TABBED_NFLAG     0x00000002  contains tab-delimited lists
 
-

§6. And the metadata is stored in this table, whose indexes are offset by +

§7. And the metadata is stored in this table, whose indexes are offset by ENUMERATED_NT_BASE. We can therefore only retrieve metadata on enumerated node types, not on meaning codes such as RULE_MC, for which the following function will return NULL. @@ -153,7 +168,7 @@ the following function will return node_type_metadata node_type_metadatas[NO_DEFINED_NT_VALUES]; -node_type_metadata *NodeType::get_metadata(node_type_t t) { +node_type_metadata *NodeType::get_metadata(node_type_t t) { if (NodeType::is_enumerated(t)) { node_type_metadata *metadata = &(node_type_metadatas[t - ENUMERATED_NT_BASE]); @@ -166,14 +181,14 @@ the following function will return return NULL; }

-

§7. Logging. In the event that metadata isn't available, because the node is not +

§8. Logging. In the event that metadata isn't available, because the node is not enumerated, we allow a callback function (if provided) to do the job for us.

-void NodeType::log(OUTPUT_STREAM, int it) {
+void NodeType::log(OUTPUT_STREAM, int it) {
     node_type_t t = (node_type_t) it;
-    node_type_metadata *metadata = NodeType::get_metadata(t);
+    node_type_metadata *metadata = NodeType::get_metadata(t);
     if (metadata) WRITE("%S", metadata->node_type_name);
     else {
     #ifdef LOG_UNENUMERATED_NODE_TYPES_SYNTAX_CALLBACK
@@ -185,10 +200,10 @@ enumerated, we allow a callback function (if provided) to do the job for us.
     }
 }
 
-

§8. Creation.

+

§9. Creation.

-void NodeType::new(node_type_t identity, text_stream *node_type_name, int min_children,
+void NodeType::new(node_type_t identity, text_stream *node_type_name, int min_children,
     int max_children, int category, int node_flags) {
     if (NodeType::is_enumerated(identity) == FALSE) internal_error("set bad metadata");
     node_type_metadata *ptnt =
@@ -201,46 +216,46 @@ enumerated, we allow a callback function (if provided) to do the job for us.
     ptnt->node_flags = node_flags;
 }
 
-

§9. Basic properties.

+

§10. Basic properties.

-int NodeType::category(node_type_t t) {
-    node_type_metadata *metadata = NodeType::get_metadata(t);
+int NodeType::category(node_type_t t) {
+    node_type_metadata *metadata = NodeType::get_metadata(t);
     if (metadata) return metadata->category;
     return INVALID_NCAT;
 }
 
-int NodeType::is_top_level(node_type_t t) {
-    if (NodeType::category(t) == L1_NCAT) return TRUE;
+int NodeType::is_top_level(node_type_t t) {
+    if (NodeType::category(t) == L1_NCAT) return TRUE;
     return FALSE;
 }
 
-int NodeType::has_flag(node_type_t t, int f) {
-    node_type_metadata *metadata = NodeType::get_metadata(t);
+int NodeType::has_flag(node_type_t t, int f) {
+    node_type_metadata *metadata = NodeType::get_metadata(t);
     if ((metadata) && ((metadata->node_flags) & f)) return TRUE;
     return FALSE;
 }
 
 text_stream *NodeType::get_name(node_type_t t) {
-    node_type_metadata *metadata = NodeType::get_metadata(t);
+    node_type_metadata *metadata = NodeType::get_metadata(t);
     if (metadata == NULL) return I"?";
     return metadata->node_type_name;
 }
 
-

§10. This provides a way for users of the module to indicate what's a sentence: +

§11. This provides a way for users of the module to indicate what's a sentence:

-int NodeType::is_sentence(node_type_t t) {
-    #ifdef SENTENCE_NODE_SYNTAX_CALLBACK
-    return SENTENCE_NODE_SYNTAX_CALLBACK(t);
+int NodeType::is_sentence(node_type_t t) {
+    #ifdef IS_SENTENCE_NODE_SYNTAX_CALLBACK
+    return IS_SENTENCE_NODE_SYNTAX_CALLBACK(t);
     #endif
-    #ifndef SENTENCE_NODE_SYNTAX_CALLBACK
+    #ifndef IS_SENTENCE_NODE_SYNTAX_CALLBACK
     return FALSE;
     #endif
 }
 
-

§11. Node types used by the syntax module. The syntax module uses only the following node types, but our client modules +

§12. Node types used by the syntax module. The syntax module uses only the following node types, but our client modules add substantially more. The three callback functions provide opportunities to do this. All a bit clumsy, but it works.

@@ -256,18 +271,18 @@ do this. All a bit clumsy, but it works. enum UNKNOWN_NT "arfle barfle gloop"
-void NodeType::metadata_setup(void) {
-    NodeType::new(INVALID_NT, I"(INVALID_NT)",    0, INFTY, INVALID_NCAT, 0);
+void NodeType::metadata_setup(void) {
+    NodeType::new(INVALID_NT, I"(INVALID_NT)",    0, INFTY, INVALID_NCAT, 0);
 
-    NodeType::new(ROOT_NT, I"ROOT_NT",            0, INFTY, L1_NCAT, DONT_VISIT_NFLAG);
-    NodeType::new(INCLUSION_NT, I"INCLUSION_NT",  0, INFTY, L1_NCAT, DONT_VISIT_NFLAG);
-    NodeType::new(HEADING_NT, I"HEADING_NT",      0, INFTY, L1_NCAT, 0);
-    NodeType::new(INCLUDE_NT, I"INCLUDE_NT",      0, 0,     L2_NCAT, 0);
-    NodeType::new(BEGINHERE_NT, I"BEGINHERE_NT",  0, 0,     L2_NCAT, 0);
-    NodeType::new(ENDHERE_NT, I"ENDHERE_NT",      0, 0,     L2_NCAT, 0);
-    NodeType::new(SENTENCE_NT, I"SENTENCE_NT",    0, INFTY, L2_NCAT, 0);
-    NodeType::new(AMBIGUITY_NT, I"AMBIGUITY_NT",  0, INFTY, L1_NCAT, 0);
-    NodeType::new(UNKNOWN_NT, I"UNKNOWN_NT",      0, INFTY, UNKNOWN_NCAT, 0);
+    NodeType::new(ROOT_NT, I"ROOT_NT",            0, INFTY, L1_NCAT, DONT_VISIT_NFLAG);
+    NodeType::new(INCLUSION_NT, I"INCLUSION_NT",  0, INFTY, L1_NCAT, DONT_VISIT_NFLAG);
+    NodeType::new(HEADING_NT, I"HEADING_NT",      0, INFTY, L1_NCAT, 0);
+    NodeType::new(INCLUDE_NT, I"INCLUDE_NT",      0, 0,     L2_NCAT, 0);
+    NodeType::new(BEGINHERE_NT, I"BEGINHERE_NT",  0, 0,     L2_NCAT, 0);
+    NodeType::new(ENDHERE_NT, I"ENDHERE_NT",      0, 0,     L2_NCAT, 0);
+    NodeType::new(SENTENCE_NT, I"SENTENCE_NT",    0, INFTY, L2_NCAT, 0);
+    NodeType::new(AMBIGUITY_NT, I"AMBIGUITY_NT",  0, INFTY, L1_NCAT, 0);
+    NodeType::new(UNKNOWN_NT, I"UNKNOWN_NT",      0, INFTY, UNKNOWN_NCAT, 0);
 
     #ifdef NODE_METADATA_SETUP_SYNTAX_CALLBACK
     NODE_METADATA_SETUP_SYNTAX_CALLBACK();
@@ -280,15 +295,15 @@ do this. All a bit clumsy, but it works.
     #endif
 }
 
-

§12. Parentage rules. It's mostly the case that node category determines whether one node can be +

§13. Parentage rules. It's mostly the case that node category determines whether one node can be parent to another, but there are exceptions.

-int NodeType::parentage_allowed(node_type_t t_parent, node_type_t t_child) {
-    node_type_metadata *metadata_parent = NodeType::get_metadata(t_parent);
+int NodeType::parentage_allowed(node_type_t t_parent, node_type_t t_child) {
+    node_type_metadata *metadata_parent = NodeType::get_metadata(t_parent);
     if (metadata_parent == NULL) return FALSE;
-    node_type_metadata *metadata_child = NodeType::get_metadata(t_child);
+    node_type_metadata *metadata_child = NodeType::get_metadata(t_child);
     if (metadata_child == NULL) return FALSE;
 
     int cat_child = metadata_child->category;
diff --git a/docs/syntax-module/2-pn.html b/docs/syntax-module/2-pn.html
index 2d600c4f6..96d04712c 100644
--- a/docs/syntax-module/2-pn.html
+++ b/docs/syntax-module/2-pn.html
@@ -78,9 +78,9 @@ MathJax = {
 		
 
-

To represent atomic pieces of meaning; or, less pretentiously, to provide the nodes of which syntax trees are made up.

+

Syntax trees are made of single nodes, each representing one way to understand a given piece of text.

-
+

§1. Nodes themselves. Each node is an instance of this:

@@ -88,87 +88,78 @@ MathJax = {
 typedef struct parse_node {
     struct wording text_parsed;  the text being interpreted by this node
-
     node_type_t node_type;  what the node basically represents
     struct parse_node_annotation *annotations;  see Node Annotations
 
     struct parse_node *down;  pointers within the current interpretation
     struct parse_node *next;
-
-    int score;  used to choose most likely interpretation
     struct parse_node *next_alternative;  fork to alternative interpretation
 
-    int log_time;  used purely as a defensive measure when writing debugging log
+    int score;  scratch storage for choosing between interpretations
+    int last_seen_on_traverse;  scratch storage for detecting accidental loops
     CLASS_DEFINITION
 } parse_node;
 
  • The structure parse_node is accessed in 2/st, 2/na, 2/tv and here.
-

§2. Various modules conventionally use this global setting to toggle debugging -log output: -

+

§2. Creation.

-int trace_sentences = FALSE;
-
-

§3. Creation.

- -
-parse_node *Node::new(node_type_t t) {
+parse_node *Node::new(node_type_t t) {
     parse_node *pn = CREATE(parse_node);
     pn->node_type = t;
-    Node::set_text(pn, EMPTY_WORDING);
+    Node::set_text(pn, EMPTY_WORDING);
     Annotations::clear(pn);
     pn->down = NULL; pn->next = NULL; pn->next_alternative = NULL;
-    pn->log_time = 0;
-    Node::set_score(pn, 0);
+    pn->last_seen_on_traverse = 0;
+    Node::set_score(pn, 0);
     return pn;
 }
 
-

§4. The following constructor routines fill out the fields in useful ways. +

§3. The following constructor routines fill out the fields in useful ways. Here's one if a word range is to be attached:

-parse_node *Node::new_with_words(node_type_t code_number, wording W) {
-    parse_node *pn = Node::new(code_number);
-    Node::set_text(pn, W);
+parse_node *Node::new_with_words(node_type_t code_number, wording W) {
+    parse_node *pn = Node::new(code_number);
+    Node::set_text(pn, W);
     return pn;
 }
 
-

§5. The attached text. +

§4. The attached text.

-wording Node::get_text(parse_node *pn) {
+wording Node::get_text(parse_node *pn) {
     if (pn == NULL) return EMPTY_WORDING;
     return pn->text_parsed;
 }
 
-void Node::set_text(parse_node *pn, wording W) {
+void Node::set_text(parse_node *pn, wording W) {
     if (pn == NULL) internal_error("tried to set words for null node");
     pn->text_parsed = W;
 }
 
-

§6. Annotations. It's easily overlooked that the single most useful piece of information +

§5. Annotations. It's easily overlooked that the single most useful piece of information at each node is its node type, accessed as follows:

-node_type_t Node::get_type(parse_node *pn) {
+node_type_t Node::get_type(parse_node *pn) {
     if (pn == NULL) return INVALID_NT;
     return pn->node_type;
 }
-int Node::is(parse_node *pn, node_type_t t) {
+int Node::is(parse_node *pn, node_type_t t) {
     if ((pn) && (pn->node_type == t)) return TRUE;
     return FALSE;
 }
 
-

§7. When setting, we have to preserve the invariant, so we clear away any +

§6. When setting, we have to preserve the invariant, so we clear away any annotations no longer relevant to the node's new identity.

-void Node::set_type(parse_node *pn, node_type_t nt) {
+void Node::set_type(parse_node *pn, node_type_t nt) {
     #ifdef IMMUTABLE_NODE
     node_type_t from = pn->node_type;
     if (IMMUTABLE_NODE(from)) {
@@ -185,14 +176,14 @@ annotations no longer relevant to the node's new identity.
     Annotations::clear(pn);
 }
 
-

§8. The integer score, used in choosing best matches: +

§7. The integer score, used in choosing best matches:

-int Node::get_score(parse_node *pn) { return pn->score; }
-void Node::set_score(parse_node *pn, int s) { pn->score = s; }
+int Node::get_score(parse_node *pn) { return pn->score; }
+void Node::set_score(parse_node *pn, int s) { pn->score = s; }
 
-

§9. Copying parse nodes. If we want to duplicate a parse node, we cannot do so with a shallow bit copy: +

§8. Copying parse nodes. If we want to duplicate a parse node, we cannot do so with a shallow bit copy: the node points to a list of its annotations, and the duplicated node would therefore point to the same list. If, subsequently, one of the two nodes were annotated further, then the other would change in synchrony, which @@ -201,18 +192,18 @@ deep copy which duplicates not only the node, but also its annotation list.

-void Node::copy(parse_node *to, parse_node *from) {
+void Node::copy(parse_node *to, parse_node *from) {
     COPY(to, from, parse_node);
     Annotations::copy(to, from);
 }
 
 parse_node *Node::duplicate(parse_node *p) {
-    parse_node *dup = Node::new(INVALID_NT);
-    Node::copy(dup, p);
+    parse_node *dup = Node::new(INVALID_NT);
+    Node::copy(dup, p);
     return dup;
 }
 
-

§10. This variation preserves links out. +

§9. This variation preserves links out.

@@ -220,34 +211,34 @@ deep copy which duplicates not only the node, but also its annotation list.
     parse_node *next_link = to->next;
     parse_node *alt_link = to->next_alternative;
     parse_node *down_link = to->down;
-    Node::copy(to, from);
+    Node::copy(to, from);
     to->next = next_link;
     to->next_alternative = alt_link;
     to->down = down_link;
 }
 
-

§11. And to deep-copy a whole subtree: +

§10. And to deep-copy a whole subtree:

 void Node::copy_subtree(parse_node *from, parse_node *to, int level) {
     if ((from == NULL) || (to == NULL)) internal_error("Null deep copy");
-    Node::copy(to, from);
+    Node::copy(to, from);
     if (from->down) {
-        to->down = Node::new(INVALID_NT);
-        Node::copy_subtree(from->down, to->down, level+1);
+        to->down = Node::new(INVALID_NT);
+        Node::copy_subtree(from->down, to->down, level+1);
     }
     if ((level>0) && (from->next)) {
-        to->next = Node::new(INVALID_NT);
-        Node::copy_subtree(from->next, to->next, level);
+        to->next = Node::new(INVALID_NT);
+        Node::copy_subtree(from->next, to->next, level);
     }
     if ((level>0) && (from->next_alternative)) {
-        to->next_alternative = Node::new(INVALID_NT);
-        Node::copy_subtree(from->next_alternative, to->next_alternative, level);
+        to->next_alternative = Node::new(INVALID_NT);
+        Node::copy_subtree(from->next_alternative, to->next_alternative, level);
     }
 }
 
-

§12. Child count.

+

§11. Child count.

 int Node::no_children(parse_node *pn) {
@@ -256,7 +247,7 @@ deep copy which duplicates not only the node, but also its annotation list.
     return c;
 }
 
-

§13. Detection of subnodes. This is needed when producing problem messages: we may need to work up from +

§12. Detection of subnodes. This is needed when producing problem messages: we may need to work up from an arbitrary leaf to the main sentence branch containing it. At any rate, given a node PN, we want to know if another node to_find lies beneath it. (This will never be called when PN is the root, and from all other @@ -269,12 +260,12 @@ wide nor deep.) parse_node *to_try; if (PN == to_find) return TRUE; for (to_try = PN->down; to_try; to_try = to_try->next) - if (Node::contains(to_try, to_find)) + if (Node::contains(to_try, to_find)) return TRUE; return FALSE; }

-

§14. The word range beneath a given node. Any given node may be the root of a subtree concerning the structure of +

§13. The word range beneath a given node. Any given node may be the root of a subtree concerning the structure of a given contiguous range of words in the original source text. The "left edge" of a node PN is the least-numbered word considered by any node at or below PN in the tree; the "right edge" is the highest-numbered @@ -289,31 +280,31 @@ for PN and the

 int Node::left_edge_of(parse_node *PN) {
     parse_node *child;
-    int l = Wordings::first_wn(Node::get_text(PN)), lc;
+    int l = Wordings::first_wn(Node::get_text(PN)), lc;
     for (child = PN->down; child; child = child->next) {
-        lc = Node::left_edge_of(child);
+        lc = Node::left_edge_of(child);
         if ((lc >= 0) && ((l == -1) || (lc < l))) l = lc;
     }
     return l;
 }
 
-

§15. Symmetrically, the right edge is found by taking the maximum word number +

§14. Symmetrically, the right edge is found by taking the maximum word number for PN and the right edges of its children.

 int Node::right_edge_of(parse_node *PN) {
     parse_node *child;
-    int r = Wordings::last_wn(Node::get_text(PN)), rc;
-    if (Wordings::first_wn(Node::get_text(PN)) < 0) r = -1;
+    int r = Wordings::last_wn(Node::get_text(PN)), rc;
+    if (Wordings::first_wn(Node::get_text(PN)) < 0) r = -1;
     for (child = PN->down; child; child = child->next) {
-        rc = Node::right_edge_of(child);
+        rc = Node::right_edge_of(child);
         if ((rc >= 0) && ((r == -1) || (rc > r))) r = rc;
     }
     return r;
 }
 
-

§16. Logging the parse tree. For most trees, logging is a fearsome prospect, but here we only mean printing +

§15. Logging the parse tree. For most trees, logging is a fearsome prospect, but here we only mean printing out a textual representation to the debugging log.

@@ -326,12 +317,10 @@ logging is a diagnostic tool, we want it to work even when Inform is sick.

-int pn_log_token = 0;
-
 void Node::log_tree(OUTPUT_STREAM, void *vpn) {
     parse_node *pn = (parse_node *) vpn;
     if (pn == NULL) { WRITE("<null-meaning-list>\n"); return; }
-    Node::log_subtree_recursively(OUT, pn, 0, 0, 1, ++pn_log_token);
+    Node::log_subtree_recursively(OUT, pn, 0, 0, 1, SyntaxTree::new_traverse_token());
 }
 
 void Node::log_subtree(OUTPUT_STREAM, void *vpn) {
@@ -340,50 +329,52 @@ logging is a diagnostic tool, we want it to work even when Inform is sick.
     WRITE("$P\n", pn);
     if (pn->down) {
         LOG_INDENT;
-        Node::log_subtree_recursively(OUT, pn->down, 0, 0, 1, ++pn_log_token);
+        Node::log_subtree_recursively(OUT, pn->down, 0, 0, 1, ++pn_log_token);
         LOG_OUTDENT;
     }
 }
 
-

§17. Either way, we recurse as follows, being careful not to make recursive calls +

§16. Either way, we recurse as follows, being careful not to make recursive calls to pursue next links, since otherwise a source text with more than 100,000 sentences or so will exceed the typical stack size Inform has to run in.

-void Node::log_subtree_recursively(OUTPUT_STREAM, parse_node *pn, int num, int of, int gen, int ltime) {
+void Node::log_subtree_recursively(OUTPUT_STREAM, parse_node *pn, int num,
+    int of, int gen, int traverse_token) {
     while (pn) {
-        if (pn->log_time == ltime) {
-            WRITE("*** Not a tree: %W ***\n", Node::get_text(pn)); return;
+        if (pn->last_seen_on_traverse == traverse_token) {
+            WRITE("*** Not a tree: %W ***\n", Node::get_text(pn)); return;
         }
-        pn->log_time = ltime;
-        Calculate num and of such that this is [num/of] if they aren't already supplied17.1;
+        pn->last_seen_on_traverse = traverse_token;
+        Calculate num and of such that this is [num/of] if they aren't already supplied16.1;
 
         if (pn == NULL) { WRITE("<null-parse-node>\n"); return; }
         if (of > 1) {
             WRITE("[%d/%d] ", num, of);
-            if (Node::get_score(pn) != 0) WRITE("(score %d) ", Node::get_score(pn));
+            if (Node::get_score(pn) != 0) WRITE("(score %d) ", Node::get_score(pn));
         }
         WRITE("$P\n", pn);
         if (pn->down) {
             LOG_INDENT;
-            Node::log_subtree_recursively(OUT, pn->down, 0, 0, gen+1, ltime);
+            Node::log_subtree_recursively(OUT, pn->down, 0, 0, gen+1, traverse_token);
             LOG_OUTDENT;
         }
-        if (pn->next_alternative) Node::log_subtree_recursively(OUT, pn->next_alternative, num+1, of, gen+1, ltime);
+        if (pn->next_alternative) Node::log_subtree_recursively(OUT,
+            pn->next_alternative, num+1, of, gen+1, traverse_token);
 
         pn = pn->next; num = 0; of = 0; gen++;
     }
 }
 
-

§17.1. When the first alternative is called, Node::log_subtree_recursively +

§16.1. When the first alternative is called, Node::log_subtree_recursively has arguments 0 and 0 for the possibility. The following code finds out the correct value for of, setting this possibility to be [1/of]. When we later iterate through other alternatives, we pass on correct values of num and of, so that this code won't be used again on the same horizontal list of possibilities.

-

Calculate num and of such that this is [num/of] if they aren't already supplied17.1 = +

Calculate num and of such that this is [num/of] if they aren't already supplied16.1 =

@@ -393,8 +384,8 @@ so that this code won't be used again on the same horizontal list of possibiliti
         num = 1;
     }
 
-
  • This code is used in §17.
-

§18. All of those routines make use of the following, which actually performs +

  • This code is used in §16.
+

§17. All of those routines make use of the following, which actually performs the log of a parse node. Note that this always produces exactly one line of text in the debugging log.

@@ -406,13 +397,14 @@ text in the debugging log. #ifdef PARSE_TREE_LOGGER PARSE_TREE_LOGGER(OUT, pn); #else - NodeType::log(OUT, (int) pn->node_type); - if (Wordings::nonempty(Node::get_text(pn))) WRITE("'%W'", Node::get_text(pn)); + NodeType::log(OUT, (int) pn->node_type); + if (Wordings::nonempty(Node::get_text(pn))) WRITE("'%W'", Node::get_text(pn)); #ifdef LINGUISTICS_MODULE Diagrams::log_node(OUT, pn); #endif switch(pn->node_type) { - case HEADING_NT: WRITE(" (level %d)", Annotations::read_int(pn, heading_level_ANNOT)); break; + case HEADING_NT: WRITE(" (level %d)", Annotations::read_int(pn, + heading_level_ANNOT)); break; } #endif int a = 0; @@ -420,7 +412,7 @@ text in the debugging log. if (a > 0) WRITE("/%d", a); }
-

§19. This is occasionally useful: +

§18. This is occasionally useful:

@@ -431,63 +423,6 @@ text in the debugging log.
     LOG("\n");
 }
 
-

§20. Ambiguity subtrees.

- -
-parse_node *Node::add_possible_reading(parse_node *existing, parse_node *reading, wording W) {
-    if (existing == NULL) return reading;
-#ifdef CORE_MODULE
-    if (Node::is(reading, UNKNOWN_NT)) return existing;
-#endif
-    if (Node::is(reading, AMBIGUITY_NT)) reading = reading->down;
-
-    if (Node::is(existing, AMBIGUITY_NT)) {
-        #ifdef CORE_MODULE
-        if (ParseTreeUsage::is_phrasal(reading))
-            for (parse_node *E = existing->down; E; E = E->next_alternative)
-                if (Node::get_type(reading) == Node::get_type(E)) {
-                    Node::add_pr_inv(E, reading);
-                    return existing;
-                }
-        #endif
-        parse_node *L = existing->down;
-        while ((L) && (L->next_alternative)) L = L->next_alternative;
-        L->next_alternative = reading;
-        return existing;
-    }
-
-    #ifdef CORE_MODULE
-    if ((ParseTreeUsage::is_phrasal(reading)) &&
-        (Node::get_type(reading) == Node::get_type(existing))) {
-        Node::add_pr_inv(existing, reading);
-        return existing;
-    }
-    #endif
-
-    parse_node *A = Node::new_with_words(AMBIGUITY_NT, W);
-    A->down = existing;
-    A->down->next_alternative = reading;
-    return A;
-}
-
-#ifdef CORE_MODULE
-void Node::add_pr_inv(parse_node *E, parse_node *reading) {
-    for (parse_node *N = reading->down->down, *next_N = (N)?(N->next_alternative):NULL; N;
-        N = next_N, next_N = (N)?(N->next_alternative):NULL)
-        Node::add_single_pr_inv(E, N);
-}
-
-void Node::add_single_pr_inv(parse_node *E, parse_node *N) {
-    E = E->down->down;
-    if (Invocations::eq(E, N)) return;
-    while ((E) && (E->next_alternative)) {
-        E = E->next_alternative;
-        if (Invocations::eq(E, N)) return;
-    }
-    E->next_alternative = N; N->next_alternative = NULL;
-}
-#endif
-
diff --git a/docs/syntax-module/2-st.html b/docs/syntax-module/2-st.html index 7dc148205..8060de29a 100644 --- a/docs/syntax-module/2-st.html +++ b/docs/syntax-module/2-st.html @@ -66,7 +66,7 @@ function togglePopup(material_id) {

To parse trees which decompose the meaning of excerpts of text, and which allow annotations to be made at each node.

-
+

§1. Optional headings tree. Within inform7 and inbuild, the code in this section is augmented by Headings (in supervisor), which gives every syntax tree an associated tree of @@ -96,16 +96,18 @@ it includes, will form a single parse_n struct parse_node *bud_parent_stack[MAX_BUD_STACK_SIZE]; struct parse_node *last_sentence; cached position in tree int allow_last_sentence_cacheing; + int trace_sentences; HEADING_TREE_SYNTAX_TYPE *headings; CLASS_DEFINITION } parse_node_tree; parse_node_tree *SyntaxTree::new(void) { parse_node_tree *T = CREATE(parse_node_tree); - T->root_node = Node::new(ROOT_NT); + T->root_node = Node::new(ROOT_NT); T->bud_parent_sp = 0; T->last_sentence = NULL; T->allow_last_sentence_cacheing = FALSE; + T->trace_sentences = FALSE; SyntaxTree::push_bud(T, T->root_node); #ifdef NEW_HEADING_TREE_SYNTAX_CALLBACK T->headings = NEW_HEADING_TREE_SYNTAX_CALLBACK(T); @@ -133,12 +135,12 @@ it includes, will form a single parse_n

-void SyntaxTree::graft_sentence(parse_node_tree *T, parse_node *new) {
+void SyntaxTree::graft_sentence(parse_node_tree *T, parse_node *new) {
     if (T->bud_parent_sp == 0) internal_error("no attachment point");
-    if (Node::get_type(new) == HEADING_NT) Adjust bud point for a heading4.1;
+    if (Node::get_type(new) == HEADING_NT) Adjust bud point for a heading4.1;
     parse_node *sentence_attachment_point = T->bud_parent_stack[T->bud_parent_sp-1];
     SyntaxTree::graft(T, new, sentence_attachment_point);
-    if (Node::get_type(new) == HEADING_NT) SyntaxTree::push_bud(T, new);
+    if (Node::get_type(new) == HEADING_NT) SyntaxTree::push_bud(T, new);
 }
 

§4.1. When what's attached is a heading node, that changes the stack. The idea @@ -156,7 +158,7 @@ pop the bud stack until we're beneath a heading node superior to Parts. if (heading_level > 0) for (int i = T->bud_parent_sp-1; i>=0; i--) { parse_node *P = T->bud_parent_stack[i]; - if ((Node::get_type(P) == HEADING_NT) && + if ((Node::get_type(P) == HEADING_NT) && (Annotations::read_int(P, heading_level_ANNOT) >= heading_level)) T->bud_parent_sp = i; } @@ -177,7 +179,7 @@ that file temporarily the debugging log stream text_stream *save_DL = DL; DL = &parse_tree_file; Streams::enable_debugging(DL); - Node::log_tree(DL, T->root_node); + Node::log_tree(DL, T->root_node); DL = save_DL; STREAM_CLOSE(&parse_tree_file); @@ -294,9 +296,9 @@ chosen start position, rather than the root of the tree. void SyntaxTree::traverse_from(parse_node *pn, void (*visitor)(parse_node *)) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_top_level(pn->node_type)) SyntaxTree::traverse_from(pn->down, visitor); + if (NodeType::is_top_level(pn->node_type)) SyntaxTree::traverse_from(pn->down, visitor); if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(pn); } } @@ -309,7 +311,7 @@ is the sole arbiter. This depends only on its node type.

 int SyntaxTree::visitable(node_type_t t) {
-    if (NodeType::has_flag(t, DONT_VISIT_NFLAG)) return FALSE;
+    if (NodeType::has_flag(t, DONT_VISIT_NFLAG)) return FALSE;
     return TRUE;
 }
 
@@ -326,10 +328,10 @@ signature text_stream *, pars void (*visitor)(text_stream *, parse_node *)) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_top_level(pn->node_type)) + if (NodeType::is_top_level(pn->node_type)) SyntaxTree::traverse_text_from(OUT, pn->down, visitor); if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(OUT, pn); } } @@ -349,10 +351,10 @@ signature parse_node *, int * void (*visitor)(parse_node *, int *), int *X) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_top_level(pn->node_type)) + if (NodeType::is_top_level(pn->node_type)) SyntaxTree::traverse_intp_from(pn->down, visitor, X); if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(pn, X); } } @@ -372,10 +374,10 @@ signature parse_node *, int * void (*visitor)(parse_node *, int *, int *), int *X, int *Y) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_top_level(pn->node_type)) + if (NodeType::is_top_level(pn->node_type)) SyntaxTree::traverse_intp_intp_from(pn->down, visitor, X, Y); if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(pn, X, Y); } } @@ -394,10 +396,10 @@ signature parse_node *, int * void (*visitor)(parse_node *, parse_node **), parse_node **X) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_top_level(pn->node_type)) + if (NodeType::is_top_level(pn->node_type)) SyntaxTree::traverse_nodep_from(pn->down, visitor, X); if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(pn, X); } } @@ -420,15 +422,15 @@ two nodes are the one being visited and its heading, respectively. parse_node *last_h0, int *N) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_top_level(pn->node_type)) { + if (NodeType::is_top_level(pn->node_type)) { parse_node *H0 = last_h0; - if ((Node::is(pn, HEADING_NT)) && + if ((Node::is(pn, HEADING_NT)) && (Annotations::read_int(pn, heading_level_ANNOT) == 0)) H0 = pn; SyntaxTree::traverse_headingwise_from(T, pn->down, visitor, H0, N); } if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(T, pn, last_h0, N); } } @@ -461,7 +463,7 @@ because no visitable nodes would ever be reached. if (SyntaxTree::visitable(pn->node_type)) if ((*visitor)(pn, from, X)) return TRUE; - if (NodeType::is_top_level(pn->node_type)) + if (NodeType::is_top_level(pn->node_type)) if (SyntaxTree::traverse_to_find_from(pn->down, visitor, pn, X)) return TRUE; } @@ -485,13 +487,13 @@ it very much. parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { if (pn == end) { current_sentence = SCS; return TRUE; } - if (NodeType::is_top_level(pn->node_type)) { + if (NodeType::is_top_level(pn->node_type)) { if (SyntaxTree::traverse_from_up_to_ip(end, pn->down, visitor, X)) { current_sentence = SCS; return TRUE; } } if (SyntaxTree::visitable(pn->node_type)) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(pn, X); } } @@ -511,7 +513,7 @@ ones. Here is a depth-first version (like all of the functions above): parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { SyntaxTree::traverse_dfirst_from(pn->down, visitor); - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; (*visitor)(pn); } current_sentence = SCS; @@ -527,13 +529,112 @@ ones. Here is a depth-first version (like all of the functions above): void SyntaxTree::traverse_wfirst_from(parse_node *pn, void (*visitor)(parse_node *)) { parse_node *SCS = current_sentence; for (; pn; pn = pn->next) { - if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; + if (NodeType::is_sentence(pn->node_type)) current_sentence = pn; SyntaxTree::traverse_wfirst_from(pn->down, visitor); (*visitor)(pn); } current_sentence = SCS; }
+

§22. Cautious traverses. When logging or verifying the tree, we cannot use the carefree functions +above: the tree might be malformed. As a way to detect cycles, we call for +a new "traverse token" — just a unique integer value — and mark all nodes +visited with that value. +

+ +
+int pn_log_token = 0;
+
+int SyntaxTree::new_traverse_token(void) {
+    return ++pn_log_token;
+}
+
+

§23. Toggling log output. Various modules conventionally use this global setting to toggle debugging +log output: +

+ +
+int SyntaxTree::is_trace_set(parse_node_tree *T) {
+    return T->trace_sentences;
+}
+
+void SyntaxTree::set_trace(parse_node_tree *T) {
+    T->trace_sentences = TRUE;
+}
+
+void SyntaxTree::clear_trace(parse_node_tree *T) {
+    T->trace_sentences = FALSE;
+}
+
+void SyntaxTree::toggle_trace(parse_node_tree *T) {
+    T->trace_sentences = (T->trace_sentences)?FALSE:TRUE;
+}
+
+

§24. Ambiguity subtrees. The following function adds a new reading to an existing interpretation +of some wording W, and return the node now representing. For example, +suppose the text "orange" can be read as a noun for fruit, a noun for colour, +or an adjective, resulting in nodes fruit_node and colour_node and adj_node. +Then: +

+ +
  • (a) SyntaxTree::add_reading(NULL, fruit_node, W) returns noun_node, +
  • (b) but SyntaxTree::add_reading(fruit_node, colour_node, W) returns this subtree: +
+
+    AMBIGUITY_NT A
+        fruit_node
+        colour_node
+
+
  • (c) and SyntaxTree::add_reading(A, adj_node, W) returns the subtree: +
+
+    AMBIGUITY_NT A
+        fruit_node
+        colour_node
+        adj_node
+
+

Thus it accumulates possible readings of a given text. +

+ +

A complication is that the following callback function is offered the chance +to amend this process in individual cases; it's called whenever reading +is about to become one of the alternatives to some existing E. If it returns +a non-null node, that's the answer, and otherwise we carry on as normal. +

+ +

(inform7 uses this to handle ambiguous phrase invocations to be sorted +out in type-checking: see Dash (in core).) +

+ +
+parse_node *SyntaxTree::add_reading(parse_node *existing, parse_node *reading, wording W) {
+    if (existing == NULL) return reading;
+    if (Node::is(reading, UNKNOWN_NT)) return existing;
+    if (Node::is(reading, AMBIGUITY_NT)) reading = reading->down;
+    if (Node::is(existing, AMBIGUITY_NT)) {
+        #ifdef AMBIGUITY_JOIN_SYNTAX_CALLBACK
+        for (parse_node *E = existing->down; E; E = E->next_alternative) {
+            parse_node *pn = AMBIGUITY_JOIN_SYNTAX_CALLBACK(E, reading);
+            if (pn) return pn;
+        }
+        #endif
+        parse_node *L = existing->down;
+        while ((L) && (L->next_alternative)) L = L->next_alternative;
+        L->next_alternative = reading;
+        return existing;
+    }
+
+    #ifdef AMBIGUITY_JOIN_SYNTAX_CALLBACK
+    parse_node *pn = AMBIGUITY_JOIN_SYNTAX_CALLBACK(existing, reading);
+    if (pn) return pn;
+    #endif
+
+    parse_node *A = Node::new_with_words(AMBIGUITY_NT, W);
+    A->down = existing;
+    A->down->next_alternative = reading;
+    return A;
+}
+
diff --git a/docs/syntax-module/2-tv.html b/docs/syntax-module/2-tv.html index 0832222d9..8d271c670 100644 --- a/docs/syntax-module/2-tv.html +++ b/docs/syntax-module/2-tv.html @@ -68,7 +68,10 @@ function togglePopup(material_id) {
-

§1. Verify integrity. The first duty of a tree is to contain no loops, and the following checks +

§1. Verify integrity. We can perform two different checks. +

+ +

The first duty of a tree is to contain no loops, and the following checks that (rejecting even undirected loops). In addition, it checks that each node has an enumerated node type, rather than a meaning code.

@@ -76,35 +79,32 @@ node has an enumerated node type, rather than a meaning code.
 int tree_stats_size = 0, tree_stats_depth = 0, tree_stats_width = 0;
 
-void VerifyTree::verify_integrity(parse_node *p, int worth_logging) {
+void VerifyTree::verify_integrity(parse_node_tree *T) {
     tree_stats_size = 0; tree_stats_depth = 0; tree_stats_width = 1;
-    VerifyTree::verify_tree_integrity_recursively(p->down, p, "down", 0, ++pn_log_token);
+    VerifyTree::verify_integrity_below(T->root_node);
+}
+
+void VerifyTree::verify_integrity_below(parse_node *p) {
+    VerifyTree::verify_integrity_r(p->down, p, "down", 0,
+        SyntaxTree::new_traverse_token());
 }
 

§2. The verification traverse is a very cautious manoeuvre: we step through -the tree, testing each branch with our outstretched foot in case it might -be illusory or broken. At the first sign of trouble we panic. +the tree, testing each branch with our outstretched foot. At the first sign +of trouble we panic.

-void VerifyTree::verify_tree_integrity_recursively(parse_node *p,
-    parse_node *from, char *way, int depth, int ltime) {
-    int width;
-    pointer_sized_int probably_an_address = (pointer_sized_int) p;
-    depth++; if (depth > tree_stats_depth) tree_stats_depth = depth;
-    for (width = 0; p; p = p->next, width++) {
-        if ((probably_an_address == 0) || (probably_an_address == -1)) {
-            LOG("Link %s broken from:\n$P", way, from);
-            Errors::set_internal_handler(NULL);
-            internal_error("Link broken in parse tree");
-        }
-        if (p->log_time == ltime) {
+void VerifyTree::verify_integrity_r(parse_node *p,
+    parse_node *from, char *way, int depth, int traverse_token) {
+    for (int width = 0; p; p = p->next, width++) {
+        if (p->last_seen_on_traverse == traverse_token) {
             LOG("Cycle found in parse tree, found %s from:\n$P", way, from);
             Errors::set_internal_handler(NULL);
             internal_error("Cycle found in parse tree");
         }
-        p->log_time = ltime;
-        node_type_t t = Node::get_type(p);
+        p->last_seen_on_traverse = traverse_token;
+        node_type_t t = Node::get_type(p);
         if (NodeType::is_enumerated(t)) tree_stats_size++;
         else {
             LOG("Invalid node type (%08x) found %s from:\n$P", (int) t, way, from);
@@ -112,52 +112,47 @@ be illusory or broken. At the first sign of trouble we panic.
             internal_error("Link broken in parse tree");
         }
         if (p->next_alternative)
-            VerifyTree::verify_tree_integrity_recursively(p->next_alternative, p, "alt", depth, ltime);
+            VerifyTree::verify_integrity_r(p->next_alternative,
+                p, "alt", depth, traverse_token);
         if (p->down)
-            VerifyTree::verify_tree_integrity_recursively(p->down, p, "down", depth, ltime);
+            VerifyTree::verify_integrity_r(p->down,
+                p, "down", depth+1, traverse_token);
+        if (width > tree_stats_width) tree_stats_width = width;
     }
-    if (width > tree_stats_width) tree_stats_width = width;
+    if (depth > tree_stats_depth) tree_stats_depth = depth;
 }
 

§3. Verify structure. The parse tree is a complicated structure, arbitrarily wide and deep, and containing many different node types, each subject to its own rules of usage. -(For instance, a SENTENCE_NT node cannot legally be beneath a -PROPER_NOUN_NT one.) This is both good and bad: bad because complexity is -always the enemy of program correctness, good because it gives us an -independent opportunity to test a great deal of what earlier code has done. -If, given every test case, we always construct a well-formed tree, we must be -doing something right. +In this second check, we ensure that nodes have acceptable parentage and +annotations — that is, parentage and annotations which fall within the +permissions set up when their node types were created.

-

The collection of rules like this which the tree must satisfy is called its -"invariant", and is expressed by the code below. Note that this is -verification, not an attempt to correct matters. If any test fails, Inform -will stop with an internal error. (If there are multiple failures, we -itemise them to the debugging log, and only produce a single internal error -at the end.) +

If any test fails, Inform will stop with an internal error. (If there are +multiple failures, we itemise them to the debugging log, and only produce +a single internal error at the end.)

We protect ourselves by first checking that the tree is intact as a structure: once we know the tree is safe to climb over, we can wander -about counting children with impunity. +about counting branches with impunity.

-void VerifyTree::verify_node(parse_node_tree *T) {
+void VerifyTree::verify_structure(parse_node_tree *T) {
     if (T->root_node == NULL) {
         Errors::set_internal_handler(NULL);
         internal_error("Root of parse tree NULL");
     }
-    VerifyTree::verify_structure(T->root_node);
+    VerifyTree::verify_structure_from(T->root_node);
 }
 
-int node_errors = 0;
-void VerifyTree::verify_structure(parse_node *p) {
-    VerifyTree::verify_integrity(p, FALSE);
-    node_errors = 0;
-    VerifyTree::verify_structure_recursively(p, NULL);
-    if (node_errors > 0) {
-        LOG("[Verification failed: %d node errors]\n", node_errors);
+void VerifyTree::verify_structure_from(parse_node *p) {
+    VerifyTree::verify_integrity_below(p);
+    int errors_found = VerifyTree::verify_structure_r(p, NULL, 0);
+    if (errors_found > 0) {
+        LOG("[Verification failed: %d node errors]\n", errors_found);
         Errors::set_internal_handler(NULL);
         internal_error("Parse tree broken");
     }
@@ -170,9 +165,9 @@ among its children.
 

-void VerifyTree::verify_structure_recursively(parse_node *p, parse_node *parent) {
-    node_type_t t = Node::get_type(p);
-    node_type_metadata *metadata = NodeType::get_metadata(t);
+int VerifyTree::verify_structure_r(parse_node *p, parse_node *parent, int ec) {
+    node_type_t t = Node::get_type(p);
+    node_type_metadata *metadata = NodeType::get_metadata(t);
     if (metadata == NULL) internal_error("broken tree should have been reported");
 
     Check rule (1) of the invariant4.1;
@@ -181,12 +176,13 @@ among its children.
 
     int children_count = 0;
     for (parse_node *q=p->down; q; q=q->next, children_count++)
-        VerifyTree::verify_structure_recursively(q, p);
+        ec += VerifyTree::verify_structure_r(q, p, ec);
 
     Check rule (4) of the invariant4.4;
 
     if (p->next_alternative)
-        VerifyTree::verify_structure_recursively(p->next_alternative, parent);
+        ec += VerifyTree::verify_structure_r(p->next_alternative, parent, ec);
+    return ec;
 }
 

§4.1. Rule (1): no INVALID nodes. @@ -197,7 +193,8 @@ among its children.

     if (t == INVALID_NT) {
-        LOG("N%d is $N, which is not allowed except temporarily\n", p->allocation_id, t);
+        LOG("N%d is $N, which is not allowed except temporarily\n",
+            p->allocation_id, t);
         Log this invariant failure4.1.1
     }
 
@@ -225,10 +222,11 @@ among its children.

-    node_type_t t_parent = Node::get_type(parent);
+    node_type_t t_parent = Node::get_type(parent);
 
-    if (!(NodeType::parentage_allowed(t_parent, t))) {
-        LOG("N%d is $N: should not be a child of $N\n", p->allocation_id, t, t_parent);
+    if (!(NodeType::parentage_allowed(t_parent, t))) {
+        LOG("N%d is $N: should not be a child of $N\n",
+            p->allocation_id, t, t_parent);
         Log this invariant failure4.1.1
     }
 
@@ -252,13 +250,16 @@ among its children. }
  • This code is used in §4.
-

§4.1.1. Log this invariant failure4.1.1 = +

§4.1.1. (Logging the root node produces an absolutely enormous output.) +

+ +

Log this invariant failure4.1.1 =

-    if (Node::is(parent, ROOT_NT)) LOG("Failing subtree:\n$T", p);
+    if (Node::is(parent, ROOT_NT)) LOG("Failing subtree:\n$T", p);
     else LOG("Failing subtree:\n$T", parent);
-    node_errors++;
+    ec++;
 
diff --git a/docs/words-module/4-prf.html b/docs/words-module/4-prf.html index 7f67f988f..4f169c43b 100644 --- a/docs/words-module/4-prf.html +++ b/docs/words-module/4-prf.html @@ -2021,7 +2021,7 @@ and the pointer result is null. if (nt->multiplicitous) { #ifdef CORE_MODULE added_to_result = QP; - acc_result = (void *) Node::add_possible_reading((parse_node *) acc_result, QP, W); + acc_result = (void *) SyntaxTree::add_reading((parse_node *) acc_result, QP, W); #endif goto Fail; } diff --git a/inbuild/supervisor-module/Chapter 6/Source Text.w b/inbuild/supervisor-module/Chapter 6/Source Text.w index d530329a7..57d35a514 100644 --- a/inbuild/supervisor-module/Chapter 6/Source Text.w +++ b/inbuild/supervisor-module/Chapter 6/Source Text.w @@ -137,7 +137,7 @@ void SourceText::bulk_of_source_loaded(void) { @ And here is the callback function. It's only ever needed for //inform7//, not for //inbuild//, which isn't in the inventions business. -@d SENTENCE_ANNOTATION_SYNTAX_CALLBACK SourceText::annotate_new_sentence +@d NEW_NONSTRUCTURAL_SENTENCE_SYNTAX_CALLBACK SourceText::annotate_new_sentence = void SourceText::annotate_new_sentence(parse_node *new) { @@ -249,6 +249,22 @@ never accidentally match in the main source text. include ... by ... | ==> 0; sfsm->nt = INCLUDE_NT; include (- ... ==> 0; sfsm->nt = INFORM6CODE_NT; +@ Rules are ordinarily detected by their colon, which divides the header from the +rest: colons are not otherwise legal in Inform. But there's an exception. If the +sentence consists of text matching the following grammar, followed by comma, +followed by more text, then the comma is read as if it's a colon and the +sentence becomes a rule. For example: + +>> Instead of going north, try entering the cage + += + ::= + instead of ... | + every turn *** | + before ... | + after ... | + when ... + @ Properly speaking, despite the definition above, language modifying sentences are nonstructural. So what are they doing here? The answer is that we need to read them early on, because they affect the way that they parse all other diff --git a/inform7/core-module/Chapter 10/Architecture of the S-Parser.w b/inform7/core-module/Chapter 10/Architecture of the S-Parser.w index 828fe86eb..f82693ccb 100644 --- a/inform7/core-module/Chapter 10/Architecture of the S-Parser.w +++ b/inform7/core-module/Chapter 10/Architecture of the S-Parser.w @@ -232,7 +232,7 @@ parse_node *ExParser::parse_with_cache(wording W, int context, nonterminal *nt) preform_lookahead_mode = plm; @; - VerifyTree::verify_structure(spec); + VerifyTree::verify_structure_from(spec); return spec; } diff --git a/inform7/core-module/Chapter 10/Verbal and Relative Clauses.w b/inform7/core-module/Chapter 10/Verbal and Relative Clauses.w index 1649487db..4d044720f 100644 --- a/inform7/core-module/Chapter 10/Verbal and Relative Clauses.w +++ b/inform7/core-module/Chapter 10/Verbal and Relative Clauses.w @@ -291,7 +291,7 @@ parse_node *ExParser::Subtrees::to_specification_inner(int SV_not_SN, wording W, one->next_alternative = NULL; parse_node *new_poss = ExParser::Subtrees::to_specification(SV_not_SN, W, one, B); if (!(Node::is(new_poss, UNKNOWN_NT))) - amb = Node::add_possible_reading(amb, new_poss, W); + amb = SyntaxTree::add_reading(amb, new_poss, W); } if (amb == NULL) amb = Specifications::new_UNKNOWN(W); return amb; @@ -304,7 +304,7 @@ parse_node *ExParser::Subtrees::to_specification_inner(int SV_not_SN, wording W, hmm->down->next_alternative = NULL; parse_node *new_poss = ExParser::Subtrees::to_specification(SV_not_SN, W, A, hmm); if (!(Node::is(new_poss, UNKNOWN_NT))) - amb = Node::add_possible_reading(amb, new_poss, W); + amb = SyntaxTree::add_reading(amb, new_poss, W); } if (amb == NULL) amb = Specifications::new_UNKNOWN(W); return amb; diff --git a/inform7/core-module/Chapter 14/Conditions.w b/inform7/core-module/Chapter 14/Conditions.w index 12102e46a..6d90395a9 100644 --- a/inform7/core-module/Chapter 14/Conditions.w +++ b/inform7/core-module/Chapter 14/Conditions.w @@ -169,7 +169,7 @@ parse_node *Conditions::attach_historic_requirement(parse_node *cond, time_perio reading->next_alternative = NULL; reading = Conditions::attach_historic_requirement(reading, tp); if (Node::is(reading, UNKNOWN_NT) == FALSE) - amb = Node::add_possible_reading(amb, + amb = SyntaxTree::add_reading(amb, reading, Node::get_text(cond)); } return amb; diff --git a/inform7/core-module/Chapter 14/Dash.w b/inform7/core-module/Chapter 14/Dash.w index c44c57bf6..6ff37b0dd 100644 --- a/inform7/core-module/Chapter 14/Dash.w +++ b/inform7/core-module/Chapter 14/Dash.w @@ -3511,3 +3511,33 @@ void Dash::experiment(wording W, int full) { LOG("$m\n", test_tree); } } + +@ + +@d AMBIGUITY_JOIN_SYNTAX_CALLBACK Dash::ambiguity_join + += +parse_node *Dash::ambiguity_join(parse_node *existing, parse_node *reading) { + if ((ParseTreeUsage::is_phrasal(reading)) && + (Node::get_type(reading) == Node::get_type(existing))) { + Dash::add_pr_inv(existing, reading); + return existing; + } + return NULL; +} + +void Dash::add_pr_inv(parse_node *E, parse_node *reading) { + for (parse_node *N = reading->down->down, *next_N = (N)?(N->next_alternative):NULL; N; + N = next_N, next_N = (N)?(N->next_alternative):NULL) + Dash::add_single_pr_inv(E, N); +} + +void Dash::add_single_pr_inv(parse_node *E, parse_node *N) { + E = E->down->down; + if (Invocations::eq(E, N)) return; + while ((E) && (E->next_alternative)) { + E = E->next_alternative; + if (Invocations::eq(E, N)) return; + } + E->next_alternative = N; N->next_alternative = NULL; +} diff --git a/inform7/core-module/Chapter 25/Compile Phrases.w b/inform7/core-module/Chapter 25/Compile Phrases.w index dea1dd88b..99bd15f6e 100644 --- a/inform7/core-module/Chapter 25/Compile Phrases.w +++ b/inform7/core-module/Chapter 25/Compile Phrases.w @@ -79,9 +79,9 @@ void Routines::Compile::routine(phrase *ph, current_sentence = ph->declaration_node; if (Phrases::Context::compile_test_head(ph, acl) == FALSE) { if (ph->declaration_node) { - VerifyTree::verify_structure(ph->declaration_node); + VerifyTree::verify_structure_from(ph->declaration_node); Routines::Compile::code_block_outer(1, ph->declaration_node->down); - VerifyTree::verify_structure(ph->declaration_node); + VerifyTree::verify_structure_from(ph->declaration_node); } current_sentence = ph->declaration_node; Phrases::Context::compile_test_tail(ph, acl); diff --git a/inform7/core-module/Chapter 6/New Verbs.w b/inform7/core-module/Chapter 6/New Verbs.w index b56d861a8..45098b647 100644 --- a/inform7/core-module/Chapter 6/New Verbs.w +++ b/inform7/core-module/Chapter 6/New Verbs.w @@ -1202,3 +1202,12 @@ void NewVerbs::tabulate_meaning(OUTPUT_STREAM, lexicon_entry *lex) { } } +@ + +@d TRACING_LINGUISTICS_CALLBACK NewVerbs::trace_parsing + += +int NewVerbs::trace_parsing(void) { + if (SyntaxTree::is_trace_set(Task::syntax_tree())) return TRUE; + return FALSE; +} diff --git a/inform7/core-module/Chapter 7/Nonstructural Sentences.w b/inform7/core-module/Chapter 7/Nonstructural Sentences.w index ec48d8025..5173f54d6 100644 --- a/inform7/core-module/Chapter 7/Nonstructural Sentences.w +++ b/inform7/core-module/Chapter 7/Nonstructural Sentences.w @@ -98,8 +98,8 @@ void Sentences::VPs::traverse(void) { } void Sentences::VPs::visit(parse_node *p) { if (Node::get_type(p) == TRACE_NT) { - trace_sentences = 1 - trace_sentences; - Log::tracing_on(trace_sentences, I"Diagramming"); + SyntaxTree::toggle_trace(Task::syntax_tree()); + Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), I"Diagramming"); } if ((Node::get_type(p) == SENTENCE_NT) && (Annotations::read_int(p, sentence_unparsed_ANNOT))) { @@ -331,7 +331,7 @@ action declarations continue with usually extensive further text: Annotations::write_int(VP_PN, verb_id_ANNOT, ASSERT_VB); SyntaxTree::graft(Task::syntax_tree(), VP_PN, nss_tree_head); - if (trace_sentences) { + if (SyntaxTree::is_trace_set(Task::syntax_tree())) { LOG("$T\n", nss_tree_head); STREAM_FLUSH(DL); } *X = 0; diff --git a/inform7/core-module/Chapter 7/Of and From.w b/inform7/core-module/Chapter 7/Of and From.w index 957fe6204..8df2c04f8 100644 --- a/inform7/core-module/Chapter 7/Of and From.w +++ b/inform7/core-module/Chapter 7/Of and From.w @@ -61,7 +61,7 @@ void Sentences::Rearrangement::further_material(void) { } void Sentences::Rearrangement::tidy_up_ofs_and_froms(void) { - VerifyTree::verify_integrity(Task::syntax_tree()->root_node, FALSE); + VerifyTree::verify_integrity(Task::syntax_tree()); SyntaxTree::traverse_wfirst(Task::syntax_tree(), Sentences::Rearrangement::traverse_for_property_names); SyntaxTree::traverse(Task::syntax_tree(), Sentences::Rearrangement::traverse_for_nonbreaking_ofs); } diff --git a/inform7/core-module/Chapter 7/Parse Tree Usage.w b/inform7/core-module/Chapter 7/Parse Tree Usage.w index bcd9c9805..ba6be102d 100644 --- a/inform7/core-module/Chapter 7/Parse Tree Usage.w +++ b/inform7/core-module/Chapter 7/Parse Tree Usage.w @@ -146,28 +146,28 @@ void ParseTreeUsage::md(void) { = void ParseTreeUsage::write_parentage_permissions(void) { - parentage_allowed[L2_NCAT][L3_NCAT] = TRUE; - parentage_allowed[L3_NCAT][L3_NCAT] = TRUE; - parentage_allowed[L2_NCAT][L4_NCAT] = TRUE; - parentage_allowed[L4_NCAT][L4_NCAT] = TRUE; - parentage_allowed[L4_NCAT][UNKNOWN_NCAT] = TRUE; + NodeType::allow_parentage_for_categories(L2_NCAT, L3_NCAT); + NodeType::allow_parentage_for_categories(L3_NCAT, L3_NCAT); + NodeType::allow_parentage_for_categories(L2_NCAT, L4_NCAT); + NodeType::allow_parentage_for_categories(L4_NCAT, L4_NCAT); + NodeType::allow_parentage_for_categories(L4_NCAT, UNKNOWN_NCAT); - parentage_allowed[L4_NCAT][LVALUE_NCAT] = TRUE; - parentage_allowed[L4_NCAT][RVALUE_NCAT] = TRUE; - parentage_allowed[L4_NCAT][COND_NCAT] = TRUE; + NodeType::allow_parentage_for_categories(L4_NCAT, LVALUE_NCAT); + NodeType::allow_parentage_for_categories(L4_NCAT, RVALUE_NCAT); + NodeType::allow_parentage_for_categories(L4_NCAT, COND_NCAT); - parentage_allowed[LVALUE_NCAT][UNKNOWN_NCAT] = TRUE; - parentage_allowed[RVALUE_NCAT][UNKNOWN_NCAT] = TRUE; - parentage_allowed[COND_NCAT][UNKNOWN_NCAT] = TRUE; - parentage_allowed[LVALUE_NCAT][LVALUE_NCAT] = TRUE; - parentage_allowed[RVALUE_NCAT][LVALUE_NCAT] = TRUE; - parentage_allowed[COND_NCAT][LVALUE_NCAT] = TRUE; - parentage_allowed[LVALUE_NCAT][RVALUE_NCAT] = TRUE; - parentage_allowed[RVALUE_NCAT][RVALUE_NCAT] = TRUE; - parentage_allowed[COND_NCAT][RVALUE_NCAT] = TRUE; - parentage_allowed[LVALUE_NCAT][COND_NCAT] = TRUE; - parentage_allowed[RVALUE_NCAT][COND_NCAT] = TRUE; - parentage_allowed[COND_NCAT][COND_NCAT] = TRUE; + NodeType::allow_parentage_for_categories(LVALUE_NCAT, UNKNOWN_NCAT); + NodeType::allow_parentage_for_categories(RVALUE_NCAT, UNKNOWN_NCAT); + NodeType::allow_parentage_for_categories(COND_NCAT, UNKNOWN_NCAT); + NodeType::allow_parentage_for_categories(LVALUE_NCAT, LVALUE_NCAT); + NodeType::allow_parentage_for_categories(RVALUE_NCAT, LVALUE_NCAT); + NodeType::allow_parentage_for_categories(COND_NCAT, LVALUE_NCAT); + NodeType::allow_parentage_for_categories(LVALUE_NCAT, RVALUE_NCAT); + NodeType::allow_parentage_for_categories(RVALUE_NCAT, RVALUE_NCAT); + NodeType::allow_parentage_for_categories(COND_NCAT, RVALUE_NCAT); + NodeType::allow_parentage_for_categories(LVALUE_NCAT, COND_NCAT); + NodeType::allow_parentage_for_categories(RVALUE_NCAT, COND_NCAT); + NodeType::allow_parentage_for_categories(COND_NCAT, COND_NCAT); } @ @@ -339,7 +339,7 @@ int ParseTreeUsage::parentage_exceptions(node_type_t t_parent, int cat_parent, @ Further classification: @d IMMUTABLE_NODE ParseTreeUsage::immutable -@d SENTENCE_NODE_SYNTAX_CALLBACK ParseTreeUsage::second_level +@d IS_SENTENCE_NODE_SYNTAX_CALLBACK ParseTreeUsage::second_level = int ParseTreeUsage::second_level(node_type_t t) { @@ -403,7 +403,7 @@ be such that their head nodes each pass this test: = int ParseTreeUsage::allow_in_assertions(parse_node *p) { - VerifyTree::verify_structure(p); + VerifyTree::verify_structure_from(p); if (NodeType::has_flag(Node::get_type(p), ASSERT_NFLAG)) return TRUE; return FALSE; } @@ -487,7 +487,8 @@ void ParseTreeUsage::log_node(OUTPUT_STREAM, parse_node *pn) { @ = void ParseTreeUsage::verify(void) { - VerifyTree::verify_node(Task::syntax_tree()); + VerifyTree::verify_integrity(Task::syntax_tree()); + VerifyTree::verify_structure(Task::syntax_tree()); } @ diff --git a/inform7/core-module/Chapter 9/To Be and To Have.w b/inform7/core-module/Chapter 9/To Be and To Have.w index d8d97812b..d9fd30704 100644 --- a/inform7/core-module/Chapter 9/To Be and To Have.w +++ b/inform7/core-module/Chapter 9/To Be and To Have.w @@ -192,7 +192,7 @@ void Assertions::Copular::make_assertion(parse_node *px, parse_node *py) { if (Assertions::Creator::consult_the_creator(px, py) == FALSE) return; } - if (trace_sentences) LOG("$T", current_sentence); + if (SyntaxTree::is_trace_set(Task::syntax_tree())) LOG("$T", current_sentence); if ((Node::get_text(px))) { if (traverse == 1) Assertions::Copular::make_existential_assertion(py); px = py; diff --git a/inform7/core-module/Chapter 9/Traverse for Assertions.w b/inform7/core-module/Chapter 9/Traverse for Assertions.w index 6d74f1f48..644f6a4f8 100644 --- a/inform7/core-module/Chapter 9/Traverse for Assertions.w +++ b/inform7/core-module/Chapter 9/Traverse for Assertions.w @@ -110,7 +110,7 @@ void Assertions::Traverse::traverse2(void) { void Assertions::Traverse::traverse(int pass) { Assertions::Traverse::new_discussion(); /* clear memory of what the subject and object of discussion are */ traverse = pass; - trace_sentences = FALSE; + SyntaxTree::clear_trace(Task::syntax_tree()); if (sentence_handlers_initialised == FALSE) @; @@ -200,7 +200,7 @@ handlers until right at the end of the program. The routine which does so, } @ = - if ((trace_sentences) && (Node::get_type(p) != TRACE_NT)) + if ((SyntaxTree::is_trace_set(Task::syntax_tree())) && (Node::get_type(p) != TRACE_NT)) LOG("\n[%W]\n", Node::get_text(p)); @; @@ -248,9 +248,9 @@ void Assertions::Traverse::switch_sentence_trace(parse_node *PN) { "so I'll note it down in the Telemetry file (if you're keeping one.)"); telemetry_recording = tr; } else { - trace_sentences = 1 - trace_sentences; - if (traverse == 1) Log::tracing_on(trace_sentences, I"Pass 1"); - else Log::tracing_on(trace_sentences, I"Pass 2"); + SyntaxTree::toggle_trace(Task::syntax_tree()); + if (traverse == 1) Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), I"Pass 1"); + else Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), I"Pass 2"); } } diff --git a/inform7/linguistics-module/Chapter 5/Verb Phrases.w b/inform7/linguistics-module/Chapter 5/Verb Phrases.w index 3d6751e98..f921a256f 100644 --- a/inform7/linguistics-module/Chapter 5/Verb Phrases.w +++ b/inform7/linguistics-module/Chapter 5/Verb Phrases.w @@ -101,9 +101,9 @@ verbs were added to the grammar. Still, it was a pity. = internal { - if (trace_sentences) { LOG("Parsing the sentence: %W\n", W); LOG_INDENT; } + if (VerbPhrases::tracing()) { LOG("Parsing the sentence: %W\n", W); LOG_INDENT; } int rv = VerbPhrases::seek(W, X, XP, 0); - if (trace_sentences) { + if (VerbPhrases::tracing()) { LOG_OUTDENT; if (rv) { LOG("Passed\n"); LOG_INDENT; @@ -137,7 +137,7 @@ which word positions might be the beginning of verb phrases. int VerbPhrases::seek(wording W, int *X, void **XP, int existential_OP_edge) { int viable[VIABILITY_MAP_SIZE]; @; - if (trace_sentences) @; + if (VerbPhrases::tracing()) @; @; return FALSE; } @@ -330,7 +330,7 @@ certainty are removed from these. } certainty = pre_certainty; if (certainty == UNKNOWN_CE) certainty = post_certainty; - if (trace_sentences) LOG("Found usage, pass %d tier %d: (%W) $w (%W)\n", + if (VerbPhrases::tracing()) LOG("Found usage, pass %d tier %d: (%W) $w (%W)\n", pass, tier->priority, ISW, vi, IOW); @ If the verb form is, say, "place in ... with ...", and we have detected the @@ -352,7 +352,8 @@ who is in the Dining Room" (note the additional "is"), it would. if (existential_OP_edge > 0) { /* i.e., if we are looking for "(S which) verbs (O)" */ if ((SW)) { /* there is indeed a "which" at the end of |SW| */ SW = GET_RW(, 1); /* so trim it off */ - if (trace_sentences) LOG("Trimmed to: (%W) $w (%W)\n", SW, vi, OW); + if (VerbPhrases::tracing()) + LOG("Trimmed to: (%W) $w (%W)\n", SW, vi, OW); } } @@ -396,7 +397,7 @@ who is in the Dining Room" (note the additional "is"), it would. usage_succeeds = TRUE; } if (usage_succeeds == FALSE) { - if (trace_sentences) LOG("$w + $p + $p : failed for lack of $p\n", + if (VerbPhrases::tracing()) LOG("$w + $p + $p : failed for lack of $p\n", vi, prep, second_prep, prep); continue; } @@ -419,7 +420,7 @@ who is in the Dining Room" (note the additional "is"), it would. usage_succeeds = TRUE; } if (usage_succeeds == FALSE) { - if (trace_sentences) LOG("$w + $p + $p : failed for lack of $p\n", + if (VerbPhrases::tracing()) LOG("$w + $p + $p : failed for lack of $p\n", vi, prep, second_prep, second_prep); continue; } @@ -450,10 +451,12 @@ representing the verb. VP_PN = VerbPhrases::accept(vf, VP_PN, SW, OW, O2W); if (VP_PN) { *XP = VP_PN; - if (trace_sentences) LOG("Accepted as $w + $p + $p\n", vi, prep, second_prep); + if (VerbPhrases::tracing()) + LOG("Accepted as $w + $p + $p\n", vi, prep, second_prep); return TRUE; } else { - if (trace_sentences) LOG("Rejected as $w + $p + $p\n", vi, prep, second_prep); + if (VerbPhrases::tracing()) + LOG("Rejected as $w + $p + $p\n", vi, prep, second_prep); } @ This routine completes the sentence diagram by adding further nodes to @@ -547,3 +550,15 @@ the exactly equivalent idea of the hat being worn by Darcy. V->next->next = NounPhrases::PN_rel( Node::get_text(V), VerbMeanings::reverse_VMT(meaning), STANDARD_RELN, O_PN); } + +@ + += +int VerbPhrases::tracing(void) { + #ifdef TRACING_LINGUISTICS_CALLBACK + return TRACING_LINGUISTICS_CALLBACK(); + #endif + #ifndef TRACING_LINGUISTICS_CALLBACK + return FALSE; + #endif +} diff --git a/inform7/linguistics-test/Chapter 1/Program Control.w b/inform7/linguistics-test/Chapter 1/Program Control.w index 4b2bffe62..ab4ef4c28 100644 --- a/inform7/linguistics-test/Chapter 1/Program Control.w +++ b/inform7/linguistics-test/Chapter 1/Program Control.w @@ -19,8 +19,6 @@ int main(int argc, char **argv) { SyntaxModule::start(); LinguisticsModule::start(); - Unit::start_diagrams(); - CommandLine::declare_heading(L"linguistics-test: a tool for testing the linguistics module\n"); CommandLine::declare_switch(TEST_DIAGRAMS_CLSW, L"test-diagrams", 2, diff --git a/inform7/linguistics-test/Chapter 1/Unit Tests.w b/inform7/linguistics-test/Chapter 1/Unit Tests.w index ee1274a40..c50dcbb6c 100644 --- a/inform7/linguistics-test/Chapter 1/Unit Tests.w +++ b/inform7/linguistics-test/Chapter 1/Unit Tests.w @@ -23,19 +23,27 @@ int Unit::allow_generally(verb_conjugation *vc, int tense, int sense, int person return TRUE; } -@h +@h Minimal Preform grammar. +Only || can ever match, since the others are wired to match +any text but then fail. = ::= - chapter ... | ==> 1 - section ... ==> 2 + chapter ... | ==> 1 + section ... ==> 2 ::= - ... ==> TRUE; return FAIL_NONTERMINAL; + ... ==> TRUE; return FAIL_NONTERMINAL; ::= - ... ==> TRUE; return FAIL_NONTERMINAL; + ... ==> TRUE; return FAIL_NONTERMINAL; + ::= + ... ==> TRUE; return FAIL_NONTERMINAL; + +@ + += ::= ==> @ @@ -54,10 +62,6 @@ int Unit::allow_generally(verb_conjugation *vc, int tense, int sense, int person = int my_first_verb = TRUE; -void Unit::start_diagrams(void) { - trace_sentences = TRUE; -} - parse_node_tree *syntax_tree = NULL; void Unit::test_diagrams(text_stream *arg) { Streams::enable_debugging(STDOUT); @@ -73,6 +77,7 @@ void Unit::test_diagrams(text_stream *arg) { text_stream *save_DL = DL; DL = STDOUT; Streams::enable_debugging(DL); + SyntaxTree::clear_trace(syntax_tree); SyntaxTree::traverse(syntax_tree, Unit::diagram); Node::log_tree(DL, syntax_tree->root_node); DL = save_DL; diff --git a/inform7/problems-test/Chapter 1/Unit Tests.w b/inform7/problems-test/Chapter 1/Unit Tests.w index 490b7af8e..ddf0a4359 100644 --- a/inform7/problems-test/Chapter 1/Unit Tests.w +++ b/inform7/problems-test/Chapter 1/Unit Tests.w @@ -7,16 +7,25 @@ How we shall test it. = parse_node_tree *syntax_tree = NULL; +@h Minimal Preform grammar. +Only || can ever match, since the others are wired to match +any text but then fail. + += ::= - chapter ... | ==> 1 - section ... ==> 2 + chapter ... | ==> 1 + section ... ==> 2 ::= - ... ==> TRUE; return FAIL_NONTERMINAL; + ... ==> TRUE; return FAIL_NONTERMINAL; ::= - ... ==> TRUE; return FAIL_NONTERMINAL; + ... ==> TRUE; return FAIL_NONTERMINAL; + ::= + ... ==> TRUE; return FAIL_NONTERMINAL; + +@ = ::= ... banana ... ==> @; diff --git a/shared/syntax-module/Chapter 2/Node Tyoes.w b/shared/syntax-module/Chapter 2/Node Tyoes.w index f54d5ed22..05cb80ecb 100644 --- a/shared/syntax-module/Chapter 2/Node Tyoes.w +++ b/shared/syntax-module/Chapter 2/Node Tyoes.w @@ -1,4 +1,4 @@ -[NodeType::] Node Tyoes. +[NodeType::] Node Types. Each node in a syntax tree has a type, which informs whether it can have child nodes, and what in general terms it means. @@ -67,10 +67,24 @@ void NodeType::make_parentage_allowed_table(void) { for (int i = 0; i < NO_DEFINED_NCAT_VALUES; i++) for (int j = 0; j < NO_DEFINED_NCAT_VALUES; j++) parentage_allowed[i][j] = FALSE; - parentage_allowed[L1_NCAT][L1_NCAT] = TRUE; + NodeType::allow_parentage_for_categories(L1_NCAT, L1_NCAT); #ifdef PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK(); #endif + #ifdef MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK + MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK(); + #endif + #ifdef EVEN_MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK + EVEN_MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK(); + #endif +} + +@ The callback function |PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK| should +call this as needed to fill in more permissions: + += +void NodeType::allow_parentage_for_categories(int A, int B) { + parentage_allowed[A][B] = TRUE; } @ The bitmap of node flags currently contains only two: @@ -164,10 +178,10 @@ text_stream *NodeType::get_name(node_type_t t) { = int NodeType::is_sentence(node_type_t t) { - #ifdef SENTENCE_NODE_SYNTAX_CALLBACK - return SENTENCE_NODE_SYNTAX_CALLBACK(t); + #ifdef IS_SENTENCE_NODE_SYNTAX_CALLBACK + return IS_SENTENCE_NODE_SYNTAX_CALLBACK(t); #endif - #ifndef SENTENCE_NODE_SYNTAX_CALLBACK + #ifndef IS_SENTENCE_NODE_SYNTAX_CALLBACK return FALSE; #endif } diff --git a/shared/syntax-module/Chapter 2/Parse Nodes.w b/shared/syntax-module/Chapter 2/Parse Nodes.w index c4de5300f..55a7fab12 100644 --- a/shared/syntax-module/Chapter 2/Parse Nodes.w +++ b/shared/syntax-module/Chapter 2/Parse Nodes.w @@ -1,7 +1,7 @@ [Node::] Parse Nodes. -To represent atomic pieces of meaning; or, less pretentiously, to provide -the nodes of which syntax trees are made up. +Syntax trees are made of single nodes, each representing one way to understand +a given piece of text. @h Nodes themselves. Each node is an instance of this: @@ -9,26 +9,18 @@ Each node is an instance of this: = typedef struct parse_node { struct wording text_parsed; /* the text being interpreted by this node */ - node_type_t node_type; /* what the node basically represents */ struct parse_node_annotation *annotations; /* see //Node Annotations// */ struct parse_node *down; /* pointers within the current interpretation */ struct parse_node *next; - - int score; /* used to choose most likely interpretation */ struct parse_node *next_alternative; /* fork to alternative interpretation */ - int log_time; /* used purely as a defensive measure when writing debugging log */ + int score; /* scratch storage for choosing between interpretations */ + int last_seen_on_traverse; /* scratch storage for detecting accidental loops */ CLASS_DEFINITION } parse_node; -@ Various modules conventionally use this global setting to toggle debugging -log output: - -= -int trace_sentences = FALSE; - @h Creation. = @@ -38,7 +30,7 @@ parse_node *Node::new(node_type_t t) { Node::set_text(pn, EMPTY_WORDING); Annotations::clear(pn); pn->down = NULL; pn->next = NULL; pn->next_alternative = NULL; - pn->log_time = 0; + pn->last_seen_on_traverse = 0; Node::set_score(pn, 0); return pn; } @@ -236,12 +228,10 @@ that we're logging a badly-formed tree; this should never happen, but since logging is a diagnostic tool, we want it to work even when Inform is sick. = -int pn_log_token = 0; - void Node::log_tree(OUTPUT_STREAM, void *vpn) { parse_node *pn = (parse_node *) vpn; if (pn == NULL) { WRITE("\n"); return; } - Node::log_subtree_recursively(OUT, pn, 0, 0, 1, ++pn_log_token); + Node::log_subtree_recursively(OUT, pn, 0, 0, 1, SyntaxTree::new_traverse_token()); } void Node::log_subtree(OUTPUT_STREAM, void *vpn) { @@ -260,12 +250,13 @@ to pursue |next| links, since otherwise a source text with more than 100,000 sentences or so will exceed the typical stack size Inform has to run in. = -void Node::log_subtree_recursively(OUTPUT_STREAM, parse_node *pn, int num, int of, int gen, int ltime) { +void Node::log_subtree_recursively(OUTPUT_STREAM, parse_node *pn, int num, + int of, int gen, int traverse_token) { while (pn) { - if (pn->log_time == ltime) { + if (pn->last_seen_on_traverse == traverse_token) { WRITE("*** Not a tree: %W ***\n", Node::get_text(pn)); return; } - pn->log_time = ltime; + pn->last_seen_on_traverse = traverse_token; @; if (pn == NULL) { WRITE("\n"); return; } @@ -276,10 +267,11 @@ void Node::log_subtree_recursively(OUTPUT_STREAM, parse_node *pn, int num, int o WRITE("$P\n", pn); if (pn->down) { LOG_INDENT; - Node::log_subtree_recursively(OUT, pn->down, 0, 0, gen+1, ltime); + Node::log_subtree_recursively(OUT, pn->down, 0, 0, gen+1, traverse_token); LOG_OUTDENT; } - if (pn->next_alternative) Node::log_subtree_recursively(OUT, pn->next_alternative, num+1, of, gen+1, ltime); + if (pn->next_alternative) Node::log_subtree_recursively(OUT, + pn->next_alternative, num+1, of, gen+1, traverse_token); pn = pn->next; num = 0; of = 0; gen++; } @@ -315,7 +307,8 @@ void Node::log_node(OUTPUT_STREAM, void *vpn) { Diagrams::log_node(OUT, pn); #endif switch(pn->node_type) { - case HEADING_NT: WRITE(" (level %d)", Annotations::read_int(pn, heading_level_ANNOT)); break; + case HEADING_NT: WRITE(" (level %d)", Annotations::read_int(pn, + heading_level_ANNOT)); break; } #endif int a = 0; @@ -332,60 +325,3 @@ void Node::log_with_annotations(parse_node *pn) { LOG("-%d", pna->annotation_id); LOG("\n"); } - -@h Ambiguity subtrees. - -= -parse_node *Node::add_possible_reading(parse_node *existing, parse_node *reading, wording W) { - if (existing == NULL) return reading; -#ifdef CORE_MODULE - if (Node::is(reading, UNKNOWN_NT)) return existing; -#endif - if (Node::is(reading, AMBIGUITY_NT)) reading = reading->down; - - if (Node::is(existing, AMBIGUITY_NT)) { - #ifdef CORE_MODULE - if (ParseTreeUsage::is_phrasal(reading)) - for (parse_node *E = existing->down; E; E = E->next_alternative) - if (Node::get_type(reading) == Node::get_type(E)) { - Node::add_pr_inv(E, reading); - return existing; - } - #endif - parse_node *L = existing->down; - while ((L) && (L->next_alternative)) L = L->next_alternative; - L->next_alternative = reading; - return existing; - } - - #ifdef CORE_MODULE - if ((ParseTreeUsage::is_phrasal(reading)) && - (Node::get_type(reading) == Node::get_type(existing))) { - Node::add_pr_inv(existing, reading); - return existing; - } - #endif - - parse_node *A = Node::new_with_words(AMBIGUITY_NT, W); - A->down = existing; - A->down->next_alternative = reading; - return A; -} - -#ifdef CORE_MODULE -void Node::add_pr_inv(parse_node *E, parse_node *reading) { - for (parse_node *N = reading->down->down, *next_N = (N)?(N->next_alternative):NULL; N; - N = next_N, next_N = (N)?(N->next_alternative):NULL) - Node::add_single_pr_inv(E, N); -} - -void Node::add_single_pr_inv(parse_node *E, parse_node *N) { - E = E->down->down; - if (Invocations::eq(E, N)) return; - while ((E) && (E->next_alternative)) { - E = E->next_alternative; - if (Invocations::eq(E, N)) return; - } - E->next_alternative = N; N->next_alternative = NULL; -} -#endif diff --git a/shared/syntax-module/Chapter 2/Syntax Trees.w b/shared/syntax-module/Chapter 2/Syntax Trees.w index e131fa2a5..07a31d359 100644 --- a/shared/syntax-module/Chapter 2/Syntax Trees.w +++ b/shared/syntax-module/Chapter 2/Syntax Trees.w @@ -30,6 +30,7 @@ typedef struct parse_node_tree { struct parse_node *bud_parent_stack[MAX_BUD_STACK_SIZE]; struct parse_node *last_sentence; /* cached position in tree */ int allow_last_sentence_cacheing; + int trace_sentences; HEADING_TREE_SYNTAX_TYPE *headings; CLASS_DEFINITION } parse_node_tree; @@ -40,6 +41,7 @@ parse_node_tree *SyntaxTree::new(void) { T->bud_parent_sp = 0; T->last_sentence = NULL; T->allow_last_sentence_cacheing = FALSE; + T->trace_sentences = FALSE; SyntaxTree::push_bud(T, T->root_node); #ifdef NEW_HEADING_TREE_SYNTAX_CALLBACK T->headings = NEW_HEADING_TREE_SYNTAX_CALLBACK(T); @@ -440,3 +442,96 @@ void SyntaxTree::traverse_wfirst_from(parse_node *pn, void (*visitor)(parse_node } current_sentence = SCS; } + +@h Cautious traverses. +When logging or verifying the tree, we cannot use the carefree functions +above: the tree might be malformed. As a way to detect cycles, we call for +a new "traverse token" -- just a unique integer value -- and mark all nodes +visited with that value. + += +int pn_log_token = 0; + +int SyntaxTree::new_traverse_token(void) { + return ++pn_log_token; +} + +@h Toggling log output. +Various modules conventionally use this global setting to toggle debugging +log output: + += +int SyntaxTree::is_trace_set(parse_node_tree *T) { + return T->trace_sentences; +} + +void SyntaxTree::set_trace(parse_node_tree *T) { + T->trace_sentences = TRUE; +} + +void SyntaxTree::clear_trace(parse_node_tree *T) { + T->trace_sentences = FALSE; +} + +void SyntaxTree::toggle_trace(parse_node_tree *T) { + T->trace_sentences = (T->trace_sentences)?FALSE:TRUE; +} + +@h Ambiguity subtrees. +The following function adds a new |reading| to an |existing| interpretation +of some wording |W|, and return the node now representing. For example, +suppose the text "orange" can be read as a noun for fruit, a noun for colour, +or an adjective, resulting in nodes |fruit_node| and |colour_node| and |adj_node|. +Then: +(a) |SyntaxTree::add_reading(NULL, fruit_node, W)| returns |noun_node|, +(b) but |SyntaxTree::add_reading(fruit_node, colour_node, W)| returns this subtree: += (text) + AMBIGUITY_NT A + fruit_node + colour_node += +(c) and |SyntaxTree::add_reading(A, adj_node, W)| returns the subtree: += (text) + AMBIGUITY_NT A + fruit_node + colour_node + adj_node += +Thus it accumulates possible readings of a given text. + +A complication is that the following callback function is offered the chance +to amend this process in individual cases; it's called whenever |reading| +is about to become one of the alternatives to some existing |E|. If it returns +a non-null node, that's the answer, and otherwise we carry on as normal. + +(//inform7// uses this to handle ambiguous phrase invocations to be sorted +out in type-checking: see //core: Dash//.) + += +parse_node *SyntaxTree::add_reading(parse_node *existing, parse_node *reading, wording W) { + if (existing == NULL) return reading; + if (Node::is(reading, UNKNOWN_NT)) return existing; + if (Node::is(reading, AMBIGUITY_NT)) reading = reading->down; + if (Node::is(existing, AMBIGUITY_NT)) { + #ifdef AMBIGUITY_JOIN_SYNTAX_CALLBACK + for (parse_node *E = existing->down; E; E = E->next_alternative) { + parse_node *pn = AMBIGUITY_JOIN_SYNTAX_CALLBACK(E, reading); + if (pn) return pn; + } + #endif + parse_node *L = existing->down; + while ((L) && (L->next_alternative)) L = L->next_alternative; + L->next_alternative = reading; + return existing; + } + + #ifdef AMBIGUITY_JOIN_SYNTAX_CALLBACK + parse_node *pn = AMBIGUITY_JOIN_SYNTAX_CALLBACK(existing, reading); + if (pn) return pn; + #endif + + parse_node *A = Node::new_with_words(AMBIGUITY_NT, W); + A->down = existing; + A->down->next_alternative = reading; + return A; +} diff --git a/shared/syntax-module/Chapter 2/Tree Verification.w b/shared/syntax-module/Chapter 2/Tree Verification.w index 3eb16592d..548d92f24 100644 --- a/shared/syntax-module/Chapter 2/Tree Verification.w +++ b/shared/syntax-module/Chapter 2/Tree Verification.w @@ -5,6 +5,8 @@ Inform contains bugs of a kind which lead to malformed syntax trees: that should never happen even if the source text being compiled is a dumpster fire. @h Verify integrity. +We can perform two different checks. + The first duty of a tree is to contain no loops, and the following checks that (rejecting even undirected loops). In addition, it checks that each node has an enumerated node type, rather than a meaning code. @@ -12,33 +14,30 @@ node has an enumerated node type, rather than a meaning code. = int tree_stats_size = 0, tree_stats_depth = 0, tree_stats_width = 0; -void VerifyTree::verify_integrity(parse_node *p, int worth_logging) { +void VerifyTree::verify_integrity(parse_node_tree *T) { tree_stats_size = 0; tree_stats_depth = 0; tree_stats_width = 1; - VerifyTree::verify_tree_integrity_recursively(p->down, p, "down", 0, ++pn_log_token); + VerifyTree::verify_integrity_below(T->root_node); +} + +void VerifyTree::verify_integrity_below(parse_node *p) { + VerifyTree::verify_integrity_r(p->down, p, "down", 0, + SyntaxTree::new_traverse_token()); } @ The verification traverse is a very cautious manoeuvre: we step through -the tree, testing each branch with our outstretched foot in case it might -be illusory or broken. At the first sign of trouble we panic. +the tree, testing each branch with our outstretched foot. At the first sign +of trouble we panic. = -void VerifyTree::verify_tree_integrity_recursively(parse_node *p, - parse_node *from, char *way, int depth, int ltime) { - int width; - pointer_sized_int probably_an_address = (pointer_sized_int) p; - depth++; if (depth > tree_stats_depth) tree_stats_depth = depth; - for (width = 0; p; p = p->next, width++) { - if ((probably_an_address == 0) || (probably_an_address == -1)) { - LOG("Link %s broken from:\n$P", way, from); - Errors::set_internal_handler(NULL); - internal_error("Link broken in parse tree"); - } - if (p->log_time == ltime) { +void VerifyTree::verify_integrity_r(parse_node *p, + parse_node *from, char *way, int depth, int traverse_token) { + for (int width = 0; p; p = p->next, width++) { + if (p->last_seen_on_traverse == traverse_token) { LOG("Cycle found in parse tree, found %s from:\n$P", way, from); Errors::set_internal_handler(NULL); internal_error("Cycle found in parse tree"); } - p->log_time = ltime; + p->last_seen_on_traverse = traverse_token; node_type_t t = Node::get_type(p); if (NodeType::is_enumerated(t)) tree_stats_size++; else { @@ -47,50 +46,45 @@ void VerifyTree::verify_tree_integrity_recursively(parse_node *p, internal_error("Link broken in parse tree"); } if (p->next_alternative) - VerifyTree::verify_tree_integrity_recursively(p->next_alternative, p, "alt", depth, ltime); + VerifyTree::verify_integrity_r(p->next_alternative, + p, "alt", depth, traverse_token); if (p->down) - VerifyTree::verify_tree_integrity_recursively(p->down, p, "down", depth, ltime); + VerifyTree::verify_integrity_r(p->down, + p, "down", depth+1, traverse_token); + if (width > tree_stats_width) tree_stats_width = width; } - if (width > tree_stats_width) tree_stats_width = width; + if (depth > tree_stats_depth) tree_stats_depth = depth; } @h Verify structure. The parse tree is a complicated structure, arbitrarily wide and deep, and containing many different node types, each subject to its own rules of usage. -(For instance, a |SENTENCE_NT| node cannot legally be beneath a -|PROPER_NOUN_NT| one.) This is both good and bad: bad because complexity is -always the enemy of program correctness, good because it gives us an -independent opportunity to test a great deal of what earlier code has done. -If, given every test case, we always construct a well-formed tree, we must be -doing something right. +In this second check, we ensure that nodes have acceptable parentage and +annotations -- that is, parentage and annotations which fall within the +permissions set up when their node types were created. -The collection of rules like this which the tree must satisfy is called its -"invariant", and is expressed by the code below. Note that this is -verification, not an attempt to correct matters. If any test fails, Inform -will stop with an internal error. (If there are multiple failures, we -itemise them to the debugging log, and only produce a single internal error -at the end.) +If any test fails, Inform will stop with an internal error. (If there are +multiple failures, we itemise them to the debugging log, and only produce +a single internal error at the end.) We protect ourselves by first checking that the tree is intact as a structure: once we know the tree is safe to climb over, we can wander -about counting children with impunity. +about counting branches with impunity. = -void VerifyTree::verify_node(parse_node_tree *T) { +void VerifyTree::verify_structure(parse_node_tree *T) { if (T->root_node == NULL) { Errors::set_internal_handler(NULL); internal_error("Root of parse tree NULL"); } - VerifyTree::verify_structure(T->root_node); + VerifyTree::verify_structure_from(T->root_node); } -int node_errors = 0; -void VerifyTree::verify_structure(parse_node *p) { - VerifyTree::verify_integrity(p, FALSE); - node_errors = 0; - VerifyTree::verify_structure_recursively(p, NULL); - if (node_errors > 0) { - LOG("[Verification failed: %d node errors]\n", node_errors); +void VerifyTree::verify_structure_from(parse_node *p) { + VerifyTree::verify_integrity_below(p); + int errors_found = VerifyTree::verify_structure_r(p, NULL, 0); + if (errors_found > 0) { + LOG("[Verification failed: %d node errors]\n", errors_found); Errors::set_internal_handler(NULL); internal_error("Parse tree broken"); } @@ -102,7 +96,7 @@ parse node and (ii) either |p| is the tree root, in which case |parent| is among its children. = -void VerifyTree::verify_structure_recursively(parse_node *p, parse_node *parent) { +int VerifyTree::verify_structure_r(parse_node *p, parse_node *parent, int ec) { node_type_t t = Node::get_type(p); node_type_metadata *metadata = NodeType::get_metadata(t); if (metadata == NULL) internal_error("broken tree should have been reported"); @@ -113,19 +107,21 @@ void VerifyTree::verify_structure_recursively(parse_node *p, parse_node *parent) int children_count = 0; for (parse_node *q=p->down; q; q=q->next, children_count++) - VerifyTree::verify_structure_recursively(q, p); + ec += VerifyTree::verify_structure_r(q, p, ec); @; if (p->next_alternative) - VerifyTree::verify_structure_recursively(p->next_alternative, parent); + ec += VerifyTree::verify_structure_r(p->next_alternative, parent, ec); + return ec; } @ Rule (1): no INVALID nodes. @ = if (t == INVALID_NT) { - LOG("N%d is $N, which is not allowed except temporarily\n", p->allocation_id, t); + LOG("N%d is $N, which is not allowed except temporarily\n", + p->allocation_id, t); @ } @@ -146,7 +142,8 @@ void VerifyTree::verify_structure_recursively(parse_node *p, parse_node *parent) node_type_t t_parent = Node::get_type(parent); if (!(NodeType::parentage_allowed(t_parent, t))) { - LOG("N%d is $N: should not be a child of $N\n", p->allocation_id, t, t_parent); + LOG("N%d is $N: should not be a child of $N\n", + p->allocation_id, t, t_parent); @ } @@ -164,7 +161,9 @@ void VerifyTree::verify_structure_recursively(parse_node *p, parse_node *parent) @ } +@ (Logging the root node produces an absolutely enormous output.) + @ = if (Node::is(parent, ROOT_NT)) LOG("Failing subtree:\n$T", p); else LOG("Failing subtree:\n$T", parent); - node_errors++; + ec++; diff --git a/shared/syntax-module/Chapter 3/Sentences.w b/shared/syntax-module/Chapter 3/Sentences.w index 2792c4496..63ffce6d5 100644 --- a/shared/syntax-module/Chapter 3/Sentences.w +++ b/shared/syntax-module/Chapter 3/Sentences.w @@ -33,10 +33,16 @@ in Preform grammar functions. @default PROBLEM_REF_SYNTAX_TYPE void @default PROJECT_REF_SYNTAX_TYPE void +@e NO_EXTENSION_POS from 0 +@e BEFORE_BEGINS_EXTENSION_POS +@e MIDDLE_EXTENSION_POS +@e AFTER_ENDS_EXTENSION_POS +@e PAST_CARING_EXTENSION_POS + = typedef struct syntax_fsm_state { source_file *sf; /* reading from this source file */ - int ext_pos; /* 0: not extension; 1: before "begins here"; 2: before "ends here"; 3: after */ + int ext_pos; /* one of the |*_EXTENSION_POS| values: where we are in an extension */ int skipping_material_at_level; int main_source_start_wn; node_type_t nt; @@ -66,7 +72,8 @@ void Sentences::reset(syntax_fsm_state *sfsm, int is_extension, sfsm->skipping_material_at_level = -1; sfsm->ref = ref; sfsm->project_ref = project_ref; - if (is_extension) sfsm->ext_pos = 1; else sfsm->ext_pos = 0; + if (is_extension) sfsm->ext_pos = BEFORE_BEGINS_EXTENSION_POS; + else sfsm->ext_pos = NO_EXTENSION_POS; } @ These are the syntax errors we will generate. @@ -311,7 +318,8 @@ or until reaching the end of the text: whichever comes first. = void Sentences::make_node(parse_node_tree *T, wording W, int stop_character) { - int heading_level = 0, begins_or_ends = 0; + int heading_level = 0; + int begins_or_ends = 0; /* 1 for "begins here", -1 for "ends here" */ parse_node *new; if (Wordings::empty(W)) internal_error("empty sentence generated"); @@ -357,9 +365,9 @@ is declared as if it were a super-heading in the text. sfsm->sf = Lexer::file_of_origin(Wordings::first_wn(W)); @ = - if ((sfsm->ext_pos == 3) && (begins_or_ends == 0)) { + if ((sfsm->ext_pos == AFTER_ENDS_EXTENSION_POS) && (begins_or_ends == 0)) { Sentences::syntax_problem(ExtSpuriouslyContinues_SYNERROR, W, sfsm->ref, 0); - sfsm->ext_pos = 4; /* to avoid multiply issuing this */ + sfsm->ext_pos = PAST_CARING_EXTENSION_POS; /* to avoid multiply issuing this */ } @ The client must define a Preform nonterminal called || @@ -370,8 +378,8 @@ important), or |-1| to mean that the sentence begins an extension, or @ = if ((W)) { switch (<>) { - case -1: if (sfsm->ext_pos > 0) begins_or_ends = 1; break; - case -2: if (sfsm->ext_pos > 0) begins_or_ends = -1; break; + case -1: if (sfsm->ext_pos != NO_EXTENSION_POS) begins_or_ends = 1; break; + case -2: if (sfsm->ext_pos != NO_EXTENSION_POS) begins_or_ends = -1; break; default: heading_level = <>; break; } } @@ -403,7 +411,8 @@ newlines automatically added at the end of the feed of any source file. if (Lexer::break_before(Wordings::last_wn(W)+1) != '\n') { int k; for (k = Wordings::last_wn(W)+1; - (k<=Wordings::last_wn(W)+8) && (kref, k); } @@ -427,9 +436,10 @@ from an extension, we need to make sure we saw both beginning and end: @ = switch (sfsm->ext_pos) { - case 1: Sentences::syntax_problem(ExtNoBeginsHere_SYNERROR, W, sfsm->ref, 0); break; - case 2: Sentences::syntax_problem(ExtNoEndsHere_SYNERROR, W, sfsm->ref, 0); break; - case 3: break; + case BEFORE_BEGINS_EXTENSION_POS: + Sentences::syntax_problem(ExtNoBeginsHere_SYNERROR, W, sfsm->ref, 0); break; + case MIDDLE_EXTENSION_POS: + Sentences::syntax_problem(ExtNoEndsHere_SYNERROR, W, sfsm->ref, 0); break; } @h Unskipped material which is not a heading. @@ -482,7 +492,7 @@ sentences and options-file sentences may have been read already.) @; if (sfsm->inside_rule_mode) - @ + @ else if (stop_character == ';') { Sentences::syntax_problem(UnexpectedSemicolon_SYNERROR, W, sfsm->ref, 0); stop_character = '.'; @@ -509,26 +519,10 @@ sentences and options-file sentences may have been read already.) /* none of that happened, so we have a SENTENCE node for certain */ Annotations::write_int(new, sentence_unparsed_ANNOT, TRUE); - #ifdef SENTENCE_ANNOTATION_SYNTAX_CALLBACK - SENTENCE_ANNOTATION_SYNTAX_CALLBACK(new); + #ifdef NEW_NONSTRUCTURAL_SENTENCE_SYNTAX_CALLBACK + NEW_NONSTRUCTURAL_SENTENCE_SYNTAX_CALLBACK(new); #endif -@ Rules are ordinarily detected by their colon, which divides the header from the -rest: colons are not otherwise legal in Inform. But there's an exception. If the -sentence consists of text matching the following grammar, followed by comma, -followed by more text, then the comma is read as if it's a colon and the -sentence becomes a rule. For example: - ->> Instead of going north, try entering the cage - -= - ::= - instead of ... | - every turn *** | - before ... | - after ... | - when ... - @ We make an exception to the exception for the serial comma used in a list of alternatives: thus the comma in "Aeschylus, Sophocles, or Euripides" does not trigger this rule. We need this exception because such lists of @@ -639,7 +633,7 @@ instead of a semicolon. We may lament this, but it is so.) @ Subsequent commands are divided by semicolons, and any failure of a semicolon to appear indicates an end of the rule. -@ = +@ = #ifdef list_node_type Node::set_type(new, list_entry_node_type); #endif diff --git a/shared/syntax-module/Contents.w b/shared/syntax-module/Contents.w index 1c2038d83..e3e1a3013 100644 --- a/shared/syntax-module/Contents.w +++ b/shared/syntax-module/Contents.w @@ -6,6 +6,7 @@ Licence: Artistic License 2.0 Preliminaries What This Module Does + How To Include This Module Chapter 1: Setting Up "Loading this module." diff --git a/shared/syntax-module/Preliminaries/How To Include This Module.w b/shared/syntax-module/Preliminaries/How To Include This Module.w new file mode 100644 index 000000000..661ce2400 --- /dev/null +++ b/shared/syntax-module/Preliminaries/How To Include This Module.w @@ -0,0 +1,113 @@ +How To Include This Module. + +What to do to make use of the syntax module in a new command-line tool. + +@h Status. +The syntax module provided as one of the "shared" Inform modules, which means +that it was built with a view to potential incorporation in multiple tools. +It can be found, for example, in //inform7//, //inbuild// and //syntax-test//, +among others. //syntax-test// may be useful as a minimal example of a tool +using //syntax//. + +By convention, the modules considered as "shared" have no dependencies on +other modules except for //foundation// and other "shared" modules. + +A tool can import //syntax// only if it also imports //foundation// and +//words//. + +@h Importing the module. +We'll use the term "parent" to mean the tool which is importing //syntax//, +that is, which will include its code and be able to use it. As with any +imported module, +(*) The contents page of the parent's web must identify and locate the +module: += (text as Inweb) +Import: somepath/syntax += +(*) The parent must call |SyntaxModule::start()| just after it starts up, and +|SyntaxModule::end()| just before it shuts down. (But just after, and just +before, the corresponding calls to //foundation//.) + +But in addition, the parent of //syntax// must define some Preform grammar: + +(*) || to recognise sentences modifying the +language which is currently being parsed; +(*) || to recognise structurally important sentences; +(*) || to recognise sentences which divide up the text, +normally headings; +(*) || to recognise sentences where a comma plays +a role normally expected to be played by a colon. + +Though compulsory, these don't need to do much: see //syntax-test: Unit Tests//. + +@h Using callbacks. +Shared modules like this one are tweaked in behaviour by defining "callback +functions". This means that the parent might provide a function of its own +which would answer a question put to it by the module, or take some action +on behalf of the module: it's a callback in the sense that the parent is +normally calling the module, but then the module calls the parent back to +ask for data or action. + +The parent must indicate which function to use by defining a constant with +a specific name as being equal to that function's name. A fictional example +would be += (text as Inweb) + @d EXPRESS_SURPRISE_SYNTAX_CALLBACK Emotions::gosh + + = + void Emotions::gosh(text_stream *OUT) { + WRITE("Good gracious!\n"); + } += +The syntax module has many callbacks, but they are all optional. The following +alphabetical list has references to fuller explanations: + +(*) |AMBIGUITY_JOIN_SYNTAX_CALLBACK| can divert ambiguous readings and prevent +them from being added to a syntax tree: see //SyntaxTree::add_reading//. + +(*) |ANNOTATION_COPY_SYNTAX_CALLBACK| can perform deep rather than shallow +copies of node annotations when these are essential: see //Annotations::copy//. + +(*) |ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK|, |MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK| +and |EVEN_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK| gives permission for nodes +of given types to have annotations with given IDs, and effectively provides a +way to create custom annotations: see //Annotations::make_annotation_allowed_table//. + +(*) |BEGIN_OR_END_HERE_SYNTAX_CALLBACK| is called when a new extension beginning +or ending sentence is found in the source text being broken into sentences: +see //Sentences::make_node//. + +(*) |LANGUAGE_ELEMENT_SYNTAX_CALLBACK| is called when a sentence is found matching +the nonterminal ||: see //Sentences::make_node//. + +(*) |LOG_UNENUMERATED_NODE_TYPES_SYNTAX_CALLBACK| is called to log a node type +not recognised as one of the enumerated |*_NT| values: see //NodeType::log//. + +(*) |NEW_HEADING_SYNTAX_CALLBACK| is called when a new heading sentence is found +in the source text being broken into sentences: see //Sentences::make_node//. + +(*) |NEW_HEADING_TREE_SYNTAX_CALLBACK| is called when a new syntax tree is being +created, and needs to be given a matching tree of headings: see //SyntaxTree::new//. + +(*) |NODE_METADATA_SETUP_SYNTAX_CALLBACK|, |MORE_NODE_METADATA_SETUP_SYNTAX_CALLBACK| +and |EVEN_MORE_NODE_METADATA_SETUP_SYNTAX_CALLBACK| adds new syntax tree node +types: see //NodeType::metadata_setup//. + +(*) |PARENTAGE_EXCEPTIONS_SYNTAX_CALLBACK| allows exceptions to the rules about +which nodes in a syntax tree can be parents of which other nodes: see +//NodeType::parentage_allowed//. + +(*) |PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK|, |MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK| +and |EVEN_MORE_PARENTAGE_PERMISSIONS_SYNTAX_CALLBACK| adds permissions for nodes +to be parents of each other: see //NodeType::make_parentage_allowed_table//. + +(*) |PROBLEM_SYNTAX_CALLBACK| is called when a syntax error is found, and can +prevent this from being issued to the terminal as an error message: see +//Sentences::syntax_problem//. + +(*) |NEW_NONSTRUCTURAL_SENTENCE_SYNTAX_CALLBACK| is called when a new, regular +sentence is found in the source text being broken into sentences: see +//Sentences::make_node//. + +(*) |IS_SENTENCE_NODE_SYNTAX_CALLBACK| is asked whether a given node represents +a regular sentence or not: see //NodeType::is_sentence//. diff --git a/shared/syntax-module/Preliminaries/What This Module Does.w b/shared/syntax-module/Preliminaries/What This Module Does.w index 33b673b4f..57c37240a 100644 --- a/shared/syntax-module/Preliminaries/What This Module Does.w +++ b/shared/syntax-module/Preliminaries/What This Module Does.w @@ -31,18 +31,7 @@ source text compiled by //inform7// is just one syntax tree. When //supervisor// manages extensions, it may generate one //parse_node_tree// object for each extension whose text it reads. Still -- there are few trees. -But there are many nodes. Syntax trees are made up of //parse_node// structures. -While these are in principle individual nodes, they effectively represent -subtrees, because they carry with them links to the nodes below. A //parse_node// -object can therefore equally represent "orange", "the orange envelope", or -"now the card is in the orange envelope". - -Meaning is an ambiguous thing, and so the tree needs to be capable of -representing multiple interpretations of the same wording. So nodes have not -only |next| and |down| links to other nodes, but also |next_alternative| links, -which -- if used -- fork the syntax tree into different possible readings. - -@ The main trunk of the tree can be grown in any sequence: call //SyntaxTree::push_bud// +@ The trunk of the tree can be grown in any sequence: call //SyntaxTree::push_bud// to begin "budding" from a particular branch, and //SyntaxTree::pop_bud// to go back to where you were. These are also used automatically to ensure that sentences arriving at //SyntaxTree::graft_sentence// are grafted under the headings to @@ -67,5 +56,55 @@ But it is also possible to graft smaller (not-whole-sentence) cuttings onto each other using //SyntaxTree::graft//, which doesn't involve the bud stack at all. +@ Meaning is an ambiguous thing, and so the tree needs to be capable of +representing multiple interpretations of the same wording. So nodes have not +only |next| and |down| links to other nodes, but also |next_alternative| links, +which -- if used -- fork the syntax tree into different possible readings. + +These are not added to the tree by grafting: that's only done for definite +meanings. Instead, multiple ambiguous readings mostly lie beneath |AMBIGUITY_NT| +nodes -- see //SyntaxTree::add_reading//. For example, we might have: += (text) + sun is orange + sun + AMBIGUITY + orange (read as being a fruit) + orange (read as being a colour) += + @ An extensive suite of functions is provided to make it easy to traverse a syntax tree, calling a visitor function on each node: see //SyntaxTree::traverse//. + +@h Nodes. +Syntax trees are made up of //parse_node// structures. While these are in +principle individual nodes, they effectively represent subtrees, because they +carry with them links to the nodes below. A //parse_node// object can +therefore equally represent "orange", "the orange envelope", or "now the card +is in the orange envelope". + +Each node carries three essential pieces of information with it: +(1) The text giving rise to it (say, "Section Five - Fruit"). +(2) A node type ID, which in broad terms says what kind of reference is being +made (say, |HEADING_NT|). The possible node types are stored in the C type +|node_type_t|, which corresponds to some metadata in a //node_type_metadata// +object: see //Node::get_type// and //NodeType::get_metadata//. +(3) A list of optional annotations, which are either integer or object-valued, +and which give specifics about the meaning (say, the level number in the +hierarchy of headings). See //Node Annotations//. + +@h Fussy, defensive, pedantry. +Safe to say that Inform includes bugs: the more defensive coding we can do, +the better. That means not only extensive logging (see //Node::log_tree//) +but also strict verification tests on every tree made (see //Tree Verification//). +(a) The only nodes allowed to exist are those for node types declared +by //NodeType::new//: more generally, see //Node Types// on metadata associated +with these. +(b) A node of type |A| can only be a child of a node of type |B| if +//NodeType::parentage_allowed// says so, and this is (mostly) a matter +of calling //NodeType::allow_parentage_for_categories// -- parentage depends +not on the type per se, but on the category of the type, which groups types +together. +(c) A node of type |A| can only have an annotation with ID |I| if +//Annotations::is_allowed// says so. To declare an annotation legal, +call |Annotations::allow(A, I)|, or |Annotations::allow_for_category(C, I)| +for the category |C| of |A|. diff --git a/shared/syntax-test/Chapter 1/Unit Tests.w b/shared/syntax-test/Chapter 1/Unit Tests.w index 49c22fb1c..1ab2100a0 100644 --- a/shared/syntax-test/Chapter 1/Unit Tests.w +++ b/shared/syntax-test/Chapter 1/Unit Tests.w @@ -2,18 +2,23 @@ How we shall test it. -@h +@h Minimal Preform grammar. +Only || can ever match, since the others are wired to match +any text but then fail. = ::= - chapter ... | ==> 1 - section ... ==> 2 + chapter ... | ==> 1 + section ... ==> 2 ::= - ... ==> TRUE; return FAIL_NONTERMINAL; + ... ==> TRUE; return FAIL_NONTERMINAL; ::= - ... ==> TRUE; return FAIL_NONTERMINAL; + ... ==> TRUE; return FAIL_NONTERMINAL; + + ::= + ... ==> TRUE; return FAIL_NONTERMINAL; @h Syntax tree. diff --git a/shared/words-module/Chapter 4/Preform.w b/shared/words-module/Chapter 4/Preform.w index d168cf109..56264ad72 100644 --- a/shared/words-module/Chapter 4/Preform.w +++ b/shared/words-module/Chapter 4/Preform.w @@ -1746,7 +1746,7 @@ and the pointer result is null. if (nt->multiplicitous) { #ifdef CORE_MODULE added_to_result = QP; - acc_result = (void *) Node::add_possible_reading((parse_node *) acc_result, QP, W); + acc_result = (void *) SyntaxTree::add_reading((parse_node *) acc_result, QP, W); #endif goto Fail; }