]> ocean-lang.org Git - ocean/blobdiff - csrc/parsergen.mdc
oceani: fix merging of conditionally-scoped variables.
[ocean] / csrc / parsergen.mdc
index 97bba421519d13d35c9230ace1722c99e9536c2b..3c816fa009a23b080597800272f1600123470702 100644 (file)
@@ -351,6 +351,7 @@ production inherits from the last symbol which has a precedence.
                        s->precedence = g->prec_levels;
                        s->assoc = assoc;
                        found += 1;
+                       t = token_next(ts);
                }
                if (found == 0)
                        err = "No symbols given on precedence line";
@@ -1173,18 +1174,37 @@ can just compare the symset and the data values together.
                        a.data[i] - b.data[i];
        }
 
+It will be helpful to know if an itemset has been "completed" or not,
+particularly for LALR where itemsets get merged, at which point they
+need to be consider for completion again.  So  a `completed` flag is needed.
+
+For correct handling of `TK_newline` when parsing, we will need to
+know which states (itemsets) can occur at the start of a line, so we
+will record a `starts_line` flag too.
+
+Finally, for handling `TK_out` we need to know where production in the
+current state started *before* the most recent indent.  A state
+doesn't usually keep details of individual productions, so we need to
+add one extra detail. `min_prefix` is the smallest non-zero number of
+symbols *before* DOT in any production in an itemset.  This will allow
+us to determine if the the most recent indent is sufficiently recent
+to cancel it against a `TK_out`.  If it was seen longer ago than the
+`min_prefix`, and if the current state cannot be reduced, then the
+indented section must have ended in the middle of a syntactic unit, so
+an error must be signaled.
+
 And now we can build the list of itemsets.  The lookup routine returns
 both a success flag and a pointer to where in the list an insert
 should happen, so we don't need to search a second time.
 
-FIXME: document min_prefix
-
 ###### declarations
        struct itemset {
                struct itemset *next;
                short state;
                struct symset items;
                struct symset go_to;
+               enum assoc assoc;
+               unsigned short precedence;
                char completed;
                char starts_line;
                int min_prefix;
@@ -1225,6 +1245,7 @@ recalculated and their LA sets updated.
 them to a data structure, of freeing them.
 
        static int add_itemset(struct grammar *g, struct symset ss,
+                              enum assoc assoc, unsigned short precedence,
                               enum grammar_type type)
        {
                struct itemset **where, *is;
@@ -1235,6 +1256,8 @@ them to a data structure, of freeing them.
                        is->state = g->states;
                        g->states += 1;
                        is->items = ss;
+                       is->assoc = assoc;
+                       is->precedence = precedence;
                        is->next = *where;
                        is->go_to = INIT_DATASET;
                        *where = is;
@@ -1281,8 +1304,16 @@ is used in the next stage.
 If any of these symbols are flagged as starting a line, then this
 state must be a `starts_line` state so now is a good time to record that.
 
-NOTE: precedence handling should happen here - I haven't written this yet
-though.
+When itemsets are created we assign a precedence to the itemset from
+the complete item, if there is one.  We ignore the possibility of
+there being two and don't (currently) handle precedence in such
+grammars.  When completing a grammar we ignore any item where DOT is
+followed by a terminal with a precedence lower (numerically higher)
+than that for the itemset.  Unless the terminal has right
+associativity, we also ignore items where the terminal has the same
+precedence.  The result is that unwanted items are still in the
+itemset, but the terminal doesn't get into the go to set, so the item
+is ineffective.
 
 ###### complete itemset
        for (i = 0; i < is->items.cnt; i++) {
@@ -1300,6 +1331,18 @@ though.
                if (bs == pr->body_size)
                        continue;
                s = pr->body[bs];
+               if (s->precedence && is->precedence &&
+                   is->precedence < s->precedence)
+                       /* This terminal has a low precedence and
+                        * shouldn't be shifted
+                        */
+                       continue;
+               if (s->precedence && is->precedence &&
+                   is->precedence == s->precedence && s->assoc != Right)
+                       /* This terminal has a matching precedence and is
+                        * not Right-associative, so we mustn't shift it.
+                        */
+                       continue;
                if (symset_find(&done, s->num) < 0) {
                        symset_add(&done, s->num, 0);
                        if (s->line_like)
@@ -1359,6 +1402,8 @@ with a pre-existing itemset).
                int j;
                unsigned short state;
                struct symbol *sym = g->symtab[done.syms[i]];
+               enum assoc assoc = Non;
+               unsigned short precedence = 0;
                struct symset newitemset = INIT_SYMSET;
                if (type >= LALR)
                        newitemset = INIT_DATASET;
@@ -1378,6 +1423,14 @@ with a pre-existing itemset).
                        if (type >= LALR)
                                la = is->items.data[j];
                        pos = symset_find(&newitemset, pr->head->num);
+                       if (bp + 1 == pr->body_size &&
+                           pr->precedence > 0 &&
+                           (precedence == 0 ||
+                            pr->precedence < precedence)) {
+                               // new itemset is reducible and has a precedence.
+                               precedence = pr->precedence;
+                               assoc = pr->assoc;
+                       }
                        if (pos < 0)
                                symset_add(&newitemset, item_num(p, bp+1), la);
                        else if (type >= LALR) {
@@ -1395,12 +1448,12 @@ with a pre-existing itemset).
                                }
                        }
                }
-               state = add_itemset(g, newitemset, type);
+               state = add_itemset(g, newitemset, assoc, precedence, type);
                if (symset_find(&is->go_to, done.syms[i]) < 0)
                        symset_add(&is->go_to, done.syms[i], state);
        }
 
-All that is left is to crate the initial itemset from production zero, and
+All that is left is to create the initial itemset from production zero, and
 with `TK_eof` as the LA set.
 
 ###### functions
@@ -1419,7 +1472,7 @@ with `TK_eof` as the LA set.
                }
                // production 0, offset 0 (with no data)
                symset_add(&first, item_num(0, 0), la);
-               add_itemset(g, first, type);
+               add_itemset(g, first, Non, 0, type);
                for (again = 0, is = g->items;
                     is;
                     is = is->next ?: again ? (again = 0, g->items) : NULL) {
@@ -1595,9 +1648,15 @@ it up a bit.  First the items, with production number and associativity.
                if (dot == pr->body_size)
                        printf(" .");
                printf(" [%d]", p);
-               if (pr->precedence)
+               if (pr->precedence && dot == pr->body_size)
                        printf(" (%d%s)", pr->precedence,
                               assoc_names[pr->assoc]);
+               if (dot < pr->body_size &&
+                   pr->body[dot]->precedence) {
+                       struct symbol *s = pr->body[dot];
+                       printf(" [%d%s]", s->precedence,
+                              assoc_names[s->assoc]);
+               }
                printf("\n");
        }
 
@@ -1642,8 +1701,11 @@ Now we can report all the item sets complete with items, LA sets, and GO TO.
                for (s = 0; s < g->states; s++) {
                        int j;
                        struct itemset *is = g->statetab[s];
-                       printf("  Itemset %d:%s min prefix=%d\n",
+                       printf("  Itemset %d:%s min prefix=%d",
                               s, is->starts_line?" (startsline)":"", is->min_prefix);
+                       if (is->precedence)
+                               printf(" %d%s", is->precedence, assoc_names[is->assoc]);
+                       printf("\n");
                        for (j = 0; j < is->items.cnt; j++) {
                                report_item(g, is->items.syms[j]);
                                if (is->items.data != NO_DATA)
@@ -1734,6 +1796,15 @@ terminals to items where that terminal could be shifted and another
 which maps terminals to items that could be reduced when the terminal
 is in look-ahead.  We report when we get conflicts between the two.
 
+As a special case, if we find a SHIFT/REDUCE conflict, where a
+terminal that could be shifted is in the lookahead set of some
+reducable item, then set check if the reducable item also have
+`TK_newline` in its lookahead set.  If it does, then a newline will
+force and reduction, but anything else can reasonably be shifts, so
+that isn't really a conflict.  Such apparent conflicts do not get
+reported.  This will not affect a "tradtional" grammar that does not
+include newlines as token.
+
        static int conflicts_slr(struct grammar *g, enum grammar_type type)
        {
                int i;
@@ -1761,7 +1832,7 @@ is in look-ahead.  We report when we get conflicts between the two.
                                                symset_add(&shifts, sym, itm);
                                }
                        }
-                       /* Now look for reduction and conflicts */
+                       /* Now look for reductions and conflicts */
                        for (j = 0; j < is->items.cnt; j++) {
                                unsigned short itm = is->items.syms[j];
                                int p = item_prod(itm);
@@ -1779,7 +1850,7 @@ is in look-ahead.  We report when we get conflicts between the two.
                                int k;
                                for (k = 0; k < la.cnt; k++) {
                                        int pos = symset_find(&shifts, la.syms[k]);
-                                       if (pos >= 0) {
+                                       if (pos >= 0 && symset_find(&la, TK_newline) < 0) {
                                                printf("  State %d has SHIFT/REDUCE conflict on ", i);
                                                prtxt(g->symtab[la.syms[k]]->name);
                                                printf(":\n");
@@ -1905,7 +1976,6 @@ The go to table is stored in a simple array of `sym` and corresponding
                short reduce_prod;
                short reduce_size;
                short reduce_sym;
-               short shift_sym;
                short starts_line;
                short min_prefix;
        };
@@ -1939,23 +2009,15 @@ The go to table is stored in a simple array of `sym` and corresponding
                for (i = 0; i < g->states; i++) {
                        struct itemset *is = g->statetab[i];
                        int j, prod = -1, prod_len;
-                       int shift_sym = -1;
-                       int shift_len = 0, shift_remain = 0;
+
                        for (j = 0; j < is->items.cnt; j++) {
                                int itm = is->items.syms[j];
                                int p = item_prod(itm);
                                int bp = item_index(itm);
                                struct production *pr = g->productions[p];
 
-                               if (bp < pr->body_size) {
-                                       if (shift_sym < 0 ||
-                                           (shift_len == bp && shift_remain > pr->body_size - bp)) {
-                                               shift_sym = pr->body[bp]->num;
-                                               shift_len = bp;
-                                               shift_remain = pr->body_size - bp;
-                                       }
+                               if (bp < pr->body_size)
                                        continue;
-                               }
                                /* This is what we reduce */
                                if (prod < 0 || prod_len < pr->body_size) {
                                        prod = p;
@@ -1964,14 +2026,14 @@ The go to table is stored in a simple array of `sym` and corresponding
                        }
 
                        if (prod >= 0)
-                               fprintf(f, "\t[%d] = { %d, goto_%d, %d, %d, %d, 0, %d, %d },\n",
+                               fprintf(f, "\t[%d] = { %d, goto_%d, %d, %d, %d, %d, %d },\n",
                                        i, is->go_to.cnt, i, prod,
                                        g->productions[prod]->body_size,
                                        g->productions[prod]->head->num,
                                        is->starts_line, is->min_prefix);
                        else
-                               fprintf(f, "\t[%d] = { %d, goto_%d, -1, -1, -1, %d, %d, %d },\n",
-                                       i, is->go_to.cnt, i, shift_sym,
+                               fprintf(f, "\t[%d] = { %d, goto_%d, -1, -1, -1, %d, %d },\n",
+                                       i, is->go_to.cnt, i,
                                        is->starts_line, is->min_prefix);
                }
                fprintf(f, "};\n\n");
@@ -2650,8 +2712,8 @@ within the stack.  If we can reduce some symbols that are all since
 the most recent indent, then we do that first.  If the minimum prefix
 of the current state then extends back before the most recent indent,
 that indent can be cancelled.  If the minimum prefix is shorter then
-the indent is premature and we must start error handling, which
-currently doesn't work at all.
+the indent had ended prematurely and we must start error handling, which
+is still a work-in-progress.
 
 `TK_newline` tokens are ignored unless the top stack frame records
 that they are permitted.  In that case they will not be considered for
@@ -2660,9 +2722,32 @@ the most recent start of line.  This is how a newline forcible
 terminates any line-like structure - we try to reduce down to at most
 one symbol for each line where newlines are allowed.
 
+When, during error handling, we discard token read in, we want to keep
+discarding until we see one that is recognised.  If we had a full set
+of LR(1) grammar states, this will mean looking in the look-ahead set,
+but we don't keep a full look-ahead set.  We only record the subset
+that leads to SHIFT.  We can, however, deduce the look-ahead set but
+looking at the SHIFT subsets for all states that we can get to by
+reducing zero or more times.  So we need a little function which
+checks if a given token is in any of these look-ahead sets.
+
 ###### parser includes
        #include "parser.h"
+
 ###### parser_run
+
+       static int in_lookahead(struct token *tk, const struct state *states, int state)
+       {
+               while (state >= 0) {
+                       if (search(&states[state], tk->num) >= 0)
+                               return 1;
+                       if (states[state].reduce_prod < 0)
+                               return 0;
+                       state = search(&states[state], states[state].reduce_sym);
+               }
+               return 0;
+       }
+
        void *parser_run(struct token_state *tokens,
                         const struct state states[],
                         int (*do_reduce)(int, void**, struct token_config*, void*),
@@ -2728,8 +2813,8 @@ one symbol for each line where newlines are allowed.
                                        parser_trace_action(trace, "Cancel");
                                        continue;
                                }
-                               // fall through and force a REDUCE (as 'shift'
-                               // will fail).
+                               // fall through to error handling as both SHIFT and REDUCE
+                               // will fail.
                        }
                        if (tk->num == TK_newline) {
                                if (!tos->newline_permitted) {
@@ -2777,16 +2862,6 @@ one symbol for each line where newlines are allowed.
                                parser_trace_action(trace, "Reduce");
                                continue;
                        }
-                       if (tk->num == TK_out) {
-                               // Indent problem - synthesise tokens to get us
-                               // out of here.
-                               fprintf(stderr, "Synthesize %d to handle indent problem\n", states[tos->state].shift_sym);
-                               shift(&p, states[tos->state].shift_sym,
-                                     0, 1, tok_copy(*tk), states);
-                               // FIXME need to report this error somehow
-                               parser_trace_action(trace, "Synthesize");
-                               continue;
-                       }
                        /* Error. We walk up the stack until we
                         * find a state which will accept TK_error.
                         * We then shift in TK_error and see what state
@@ -2798,9 +2873,9 @@ one symbol for each line where newlines are allowed.
                        short indents = 0, start_of_line;
 
                        err_tk = tok_copy(*tk);
-                       while (shift(&p, TK_error, 0, 0,
-                                    err_tk, states) == 0
-                              && p.tos > 0)
+                       while (p.tos > 0 &&
+                              shift(&p, TK_error, 0, 0,
+                                    err_tk, states) == 0)
                                // discard this state
                                indents += pop(&p, 1, &start_of_line, do_free);
                        if (p.tos == 0) {
@@ -2809,7 +2884,7 @@ one symbol for each line where newlines are allowed.
                                break;
                        }
                        tos = &p.stack[p.tos-1];
-                       while (search(&states[tos->state], tk->num) < 0 &&
+                       while (!in_lookahead(tk, states, tos->state) &&
                               tk->num != TK_eof) {
                                free(tk);
                                tk = tok_copy(token_next(tokens));
@@ -2822,11 +2897,7 @@ one symbol for each line where newlines are allowed.
                                        // FIXME update since_indent here
                                }
                        }
-                       if (p.tos == 0 && tk->num == TK_eof)
-                               break;
-                       tos = &p.stack[p.tos-1];
                        tos->indents += indents;
-                       exit(1);
                }
                free(tk);
                pop(&p, p.tos, NULL, do_free);
@@ -2998,6 +3069,9 @@ an error.
 
 # calc: grammar
 
+       $LEFT * /
+       $LEFT + -
+
        Session -> Session Line
                | Line
 
@@ -3020,13 +3094,9 @@ an error.
                | ERROR NEWLINE ${ printf("Skipped a bad line\n"); }$
 
        $number
-       Expression -> Expression + Term ${ mpq_init($0.val); mpq_add($0.val, $1.val, $3.val); }$
-               | Expression - Term ${ mpq_init($0.val); mpq_sub($0.val, $1.val, $3.val); }$
-               | Term ${ mpq_init($0.val); mpq_set($0.val, $1.val); }$
-
-       Term -> Term * Factor ${ mpq_init($0.val); mpq_mul($0.val, $1.val, $3.val); }$
-               | Term / Factor ${ mpq_init($0.val); mpq_div($0.val, $1.val, $3.val); }$
-               | Factor ${ mpq_init($0.val); mpq_set($0.val, $1.val); }$
-
-       Factor -> NUMBER ${ if (number_parse($0.val, $0.tail, $1.txt) == 0) mpq_init($0.val); }$
+       Expression -> Expression + Expression ${ mpq_init($0.val); mpq_add($0.val, $1.val, $3.val); }$
+               | Expression - Expression ${ mpq_init($0.val); mpq_sub($0.val, $1.val, $3.val); }$
+               | Expression * Expression ${ mpq_init($0.val); mpq_mul($0.val, $1.val, $3.val); }$
+               | Expression / Expression ${ mpq_init($0.val); mpq_div($0.val, $1.val, $3.val); }$
+               | NUMBER ${ if (number_parse($0.val, $0.tail, $1.txt) == 0) mpq_init($0.val); }$
                | ( Expression ) ${ mpq_init($0.val); mpq_set($0.val, $2.val); }$