]> ocean-lang.org Git - ocean/commitdiff
parsergen: implement new handling of IN/OUT and NEWLINE
authorNeilBrown <neil@brown.name>
Wed, 10 Mar 2021 00:38:55 +0000 (11:38 +1100)
committerNeilBrown <neil@brown.name>
Wed, 10 Mar 2021 01:01:11 +0000 (12:01 +1100)
IN/OUT are now expected in the grammar.
In a state where an IN can be shifted, IN symbols are significant to the
grammar.  IN symbols appearing anywhere else are ignored (except for how
they affect NEWLINEs).

OUT symbols are ignored precisely when the matching IN was ignored.

NEWLINEs are ignored if the most recent IN was ignored, otherwise they
are significant for the grammar.

Signed-off-by: NeilBrown <neil@brown.name>
csrc/parsergen.mdc

index 554e63c37c921faea604399f7a9dd1f968b7cc84..b78a00d3511e7825bcbe9ef8cbc7340b8ed66fae 100644 (file)
@@ -5,6 +5,9 @@ fragments, analyses it, and can report details about the analysis and
 write out C code files which can be compiled to make a parser.
 
 "2D support" means that indentation and line breaks can be significant.
+Indent tokens (IN and OUT) and end-of-line (EOL) tokens can be used to
+describe the grammar and the parser can selectively ignore these where
+they aren't relevant.
 
 There are several distinct sections.
 
@@ -2638,6 +2641,13 @@ bottom of stack holds the start state but no symbol, as nothing came
 before the beginning.  As we need to store some value, `TK_eof` is used
 to mark the beginning of the file as well as the end.
 
+Indents (IN) are sometimes shifted and sometimes only accounted.
+Whatever decision is made must apply equally to the matching OUT.  To
+ensure this we keep a stack of bits in `ignored_indents` and
+`indent_depth`.  When we process an IN, we record if it was ignored.
+When we see an out, we pop of the relavant bit and use it to decide how
+to handle the OUT.
+
 ###### parser functions
 
        struct parser {
@@ -2648,6 +2658,8 @@ to mark the beginning of the file as well as the end.
                void **asn_stack;
                int stack_size;
                int tos;
+
+               ## parser state
        };
 
 #### Shift and pop
@@ -2721,8 +2733,8 @@ in.
 ### The heart of the parser.
 
 Now we have the parser.  For each token we might shift it, trigger a
-reduction, or start error handling.  2D tokens (IN, OUT, EOL) also need
-to be handled.
+reduction, or start error handling.  2D tokens (IN, OUT, NEWLINE) might
+also be ignored.  Ignoring tokens is combined with shifting.
 
 ###### parser vars
 
@@ -2745,73 +2757,37 @@ to be handled.
                ## handle error
        }
 
+Indents are ignored unless they can be shifted onto the stack
+immediately.  The end of an indented section - the OUT token - is
+ignored precisely when the indent was ignored.  To keep track of this we
+need a small stack of flags, which is easily stored as bits in an
+`unsigned long`.  This will never overflow and the scanner only allows
+20 levels of indentation.
 
-We return whatever `asn` was returned by reducing production zero.
-
-When we find `TK_in` and `TK_out` tokens which report indents we need
-to handle them directly as the grammar cannot express what we want to
-do with them.
-
-`TK_in` tokens are easy: we simply update indent count in the top stack frame to
-record how many indents there are following the previous token.
-
-`TK_out` tokens must be canceled against an indent count
-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 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
-shifting if it is possible to reduce some symbols that are all since
-the most recent start of line.  This is how a newline forcibly
-terminates any line-like structure - we try to reduce down to at most
-one symbol for each line where newlines are allowed.
-A consequence of this is that a rule like
-
-###### Example: newlines - broken
+###### parser state
+       unsigned long ignored_indents;
+       int indent_depth;
 
-       Newlines ->
-               | NEWLINE Newlines
-       IfStatement -> Newlines if ....
-
-cannot work, as the NEWLINE will never be shifted as the empty string
-will be reduced first.  Optional sets of newlines need to be include
-in the thing that preceed:
-
-###### Example: newlines - works
-
-       If -> if
-               | NEWLINE If
-       IfStatement -> If ....
-
-Here the NEWLINE will be shifted because nothing can be reduced until
-the `if` is seen.
+NEWLINE is ignored when in an indented section of text which was not
+explicitly expected by the grammar.  So if the most recent indent is
+ignored, so is any EOL token.
 
 For other tokens, we shift the next token if that is possible, otherwise
 we try to reduce a production.
 
 ###### try shift or ignore
 
-       if (tk->num == TK_in) {
+       if ((tk->num == TK_newline || tk->num == TK_out) &&
+           (p.ignored_indents & (1 << p.indent_depth))) {
+               /* indented, so ignore OUT and NEWLINE */
+               if (tk->num == TK_out)
+                       p.indent_depth -= 1;
                free(tk);
                tk = NULL;
-               parser_trace_action(trace, "Record");
+               parser_trace_action(trace, "Ignore");
                continue;
        }
-       if (tk->num == TK_out) {
-               if (1) {
-                       // OK to cancel
-                               free(tk);
-                       tk = NULL;
-                       parser_trace_action(trace, "Cancel");
-                       continue;
-               }
-               // fall through to error handling as both SHIFT and REDUCE
-               // will fail.
-       }
+
        if (tk->num == TK_newline) {
                if (1) {
                        free(tk);
@@ -2821,12 +2797,28 @@ we try to reduce a production.
                }
        }
        if (shift(&p, tk->num, tk, states)) {
+               if (tk->num == TK_out)
+                       p.indent_depth -= 1;
+               if (tk->num == TK_in) {
+                       p.indent_depth += 1;
+                       p.ignored_indents &= ~(1 << p.indent_depth);
+               }
                tk = NULL;
                parser_trace_action(trace, "Shift");
                ## did shift
                continue;
        }
 
+       if (tk->num == TK_in) {
+               /* No indent expected here, so ignore IN */
+               free(tk);
+               tk = NULL;
+               p.indent_depth += 1;
+               p.ignored_indents |= (1 << p.indent_depth);
+               parser_trace_action(trace, "Ignore");
+               continue;
+       }
+
 We have already discussed the bulk of the handling of a "reduce" action,
 with the `pop()` and `shift()` functions doing much of the work.  There
 is a little more complexity needed to manage storage for the asn (Abstract