}
}
-### Setting `can_eol` and `starts_line`
+### Setting `can_eol` and `line_like`
In order to be able to ignore newline tokens when not relevant, but
still include them in the parse when needed, we will need to know
which states can start a "line-like" section of code. We ignore
newlines when there is an indent since the most recent start of a
-line-like section.
+line-like symbol.
-To know what is line-like, we first need to know which symbols can end
-a line-like section, which is precisely those which can end with a
-newline token. These symbols don't necessarily alway end with a
-newline, but they can. Hence they are not described as "lines" but
-only "line-like".
+To know which symbols are line-like, we first need to know which
+symbols start with a NEWLINE token. Any symbol which is followed by a
+NEWLINE, or anything that starts with a NEWLINE, is deemed to be a line-like symbol.
+Certainly when trying to parse one of these we must take not of NEWLINEs.
-Clearly the `TK_newline` token can end with a newline. Any symbol
-which is the head of a production that contains a line-ending symbol
-followed only by nullable symbols is also a line-ending symbol. We
-use a new field `can_eol` to record this attribute of symbols, and
-compute it in a repetitive manner similar to `set_nullable`.
+Clearly the `TK_newline` token can start with a NEWLINE. Any symbol
+which is the head of a production that contains a starts-with-NEWLINE
+symbol preceeded only by nullable symbols is also a
+starts-with-NEWLINE symbol. We use a new field `can_eol` to record
+this attribute of symbols, and compute it in a repetitive manner
+similar to `set_nullable`.
+
+Once we have that, we can determine which symbols are `line_like` be
+seeing which are followed by a `can_eol` symbol in any production.
###### symbol fields
int can_eol;
- int starts_line;
+ int line_like;
###### functions
static void set_can_eol(struct grammar *g)
if (pr->head->can_eol)
continue;
- for (s = pr->body_size - 1; s >= 0; s--) {
+ for (s = 0 ; s < pr->body_size; s++) {
if (pr->body[s]->can_eol) {
pr->head->can_eol = 1;
check_again = 1;
}
}
- static void set_starts_line(struct grammar *g)
+ static void set_line_like(struct grammar *g)
{
int p;
for (p = 0; p < g->production_count; p++) {
struct production *pr = g->productions[p];
int s;
- for (s = 0; s < pr->body_size - 1; s++)
+ for (s = 1; s < pr->body_size; s++)
if (pr->body[s]->can_eol)
- pr->body[s+1]->starts_line = 1;
+ pr->body[s-1]->line_like = 1;
}
}
struct symset go_to;
char completed;
char starts_line;
+ int min_prefix;
};
###### grammar fields
struct symset LA = INIT_SYMSET;
unsigned short sn = 0;
+ if (is->min_prefix == 0 ||
+ (bs > 0 && bs < is->min_prefix))
+ is->min_prefix = bs;
if (bs == pr->body_size)
continue;
s = pr->body[bs];
if (symset_find(&done, s->num) < 0) {
symset_add(&done, s->num, 0);
- if (s->starts_line)
+ if (s->line_like)
is->starts_line = 1;
}
if (s->type != Nonterminal)
set_nullable(g);
set_can_eol(g);
- set_starts_line(g);
+ set_line_like(g);
if (type >= SLR)
build_first(g);
Firstly we have the complete list of symbols, together with the
"FIRST" set if that was generated. We add a mark to each symbol to
-show if it can end in a newline (`>`), if it implies the start of a
-line (`<`), or if it is nullable (`.`).
+show if it can end in a newline (`>`), if it is considered to be
+"line-like" (`<`), or if it is nullable (`.`).
###### functions
printf(" %c%c%c%3d%c: ",
s->nullable ? '.':' ',
s->can_eol ? '>':' ',
- s->starts_line ? '<':' ',
+ s->line_like ? '<':' ',
s->num, symtypes[s->type]);
prtxt(s->name);
if (s->precedence)
for (s = 0; s < g->states; s++) {
int j;
struct itemset *is = g->statetab[s];
- printf(" Itemset %d:%s\n", s, is->starts_line?" (startsline)":"");
+ printf(" Itemset %d:%s min prefix=%d\n",
+ s, is->starts_line?" (startsline)":"", is->min_prefix);
for (j = 0; j < is->items.cnt; j++) {
report_item(g, is->items.syms[j]);
if (is->items.data != NO_DATA)
short reduce_sym;
short shift_sym;
short starts_line;
+ short min_prefix;
};
}
if (prod >= 0)
- fprintf(f, "\t[%d] = { %d, goto_%d, %d, %d, %d, 0, %d },\n",
+ fprintf(f, "\t[%d] = { %d, goto_%d, %d, %d, %d, 0, %d, %d },\n",
i, is->go_to.cnt, i, prod,
g->productions[prod]->body_size,
g->productions[prod]->head->num,
- is->starts_line);
+ is->starts_line, is->min_prefix);
else
- fprintf(f, "\t[%d] = { %d, goto_%d, -1, -1, -1, %d, %d },\n",
+ fprintf(f, "\t[%d] = { %d, goto_%d, -1, -1, -1, %d, %d, %d },\n",
i, is->go_to.cnt, i, shift_sym,
- is->starts_line);
+ is->starts_line, is->min_prefix);
}
fprintf(f, "};\n\n");
}
freeing function. The symbol leads us to the right free function through
`do_free`.
-The `indents` count and the `starts_indented` flag track the line
-indents in the symbol. These are used to allow indent information to
+The `indents` count tracks the line indents with in the symbol or
+immediately follow it. These are used to allow indent information to
guide parsing and error recovery.
`since_newline` tracks how many stack frames since the last
start-of-line (whether indented or not). So if `since_newline` is
-zero, then this symbol is at the start of a line.
+zero, then this symbol is at the start of a line. Similarly
+`since_indent` counts the number of states since an indent, it is zero
+precisely when `indents` is not zero.
`newline_permitted` keeps track of whether newlines should be ignored
-or not, and `starts_line` records if this state stated on a newline.
+or not.
The stack is most properly seen as alternating states and symbols -
states, like the 'DOT' in items, are between symbols. Each frame in
our stack holds a state and the symbol that was before it. The
-bottom of stack holds the start state, but no symbol, as nothing came
+bottom of stack holds the start state but no symbol, as nothing came
before the beginning.
###### parser functions
short newline_permitted;
short sym;
- short starts_indented;
short indents;
- short starts_newline;
+ short since_newline;
+ short since_indent;
} *stack;
void **asn_stack;
int stack_size;
Two operations are needed on the stack - shift (which is like push) and pop.
-Shift applies not only to terminals but also to non-terminals. When we
-reduce a production we will pop off entries corresponding to the body
-symbols, then push on an item for the head of the production. This last is
-exactly the same process as shifting in a terminal so we use the same
-function for both. In both cases we provide a stack frame which
-contains the symbol to shift and related indent information.
+Shift applies not only to terminals but also to non-terminals. When
+we reduce a production we will pop off entries corresponding to the
+body symbols, then push on an item for the head of the production.
+This last is exactly the same process as shifting in a terminal so we
+use the same function for both. In both cases we provide the symbol,
+the number of indents the symbol contains (which will be zero for a
+terminal symbol) and a flag indicating the the symbol was at (or was
+reduced from a symbol which was at) the start of a line. The state is
+deduced from the current top-of-stack state and the new symbol.
To simplify other code we arrange for `shift` to fail if there is no `goto`
state for the symbol. This is useful in basic parsing due to our design
`shift` is also used to push state zero onto the stack, so if the
stack is empty, it always chooses zero as the next state.
-So `shift` finds the next state. If that succeed it extends the allocations
-if needed and pushes all the information onto the stacks.
-
-Newlines are permitted after a starts_line state until an internal
-indent. So we need to find the topmost state which `starts_line` and
-see if there are any indents other than immediately after it.
+So `shift` finds the next state. If that succeeds it extends the
+allocations if needed and pushes all the information onto the stacks.
-So we walk down:
-
-- if state starts_line, then newlines_permitted.
-- if any non-initial indents, newlines not permitted
+Newlines are permitted after a `starts_line` state until an internal
+indent. If the new frame has neither a `starts_line` state nor an
+indent, newlines are permitted if the previous stack frame permitted
+them.
###### parser functions
- static int shift(struct parser *p, struct frame *next,
+ static int shift(struct parser *p,
+ short sym, short indents, short start_of_line,
void *asn,
const struct state states[])
{
// Push an entry onto the stack
+ struct frame next = {0};
int newstate = p->tos
? search(&states[p->stack[p->tos-1].state],
- next->sym)
+ sym)
: 0;
if (newstate < 0)
return 0;
p->asn_stack = realloc(p->asn_stack, p->stack_size
* sizeof(p->asn_stack[0]));
}
- next->state = newstate;
- next->newline_permitted = 0;
- if (p->tos)
- next->newline_permitted =
- (p->stack[p->tos-1].newline_permitted?:-1)+1;
- if (next->indents > next->starts_indented)
- next->newline_permitted = 0;
- if (next->indents && next->newline_permitted > 2)
- next->newline_permitted = 0;
+ next.sym = sym;
+ next.indents = indents;
+ next.state = newstate;
if (states[newstate].starts_line)
- next->newline_permitted = 1;
- p->stack[p->tos] = *next;
+ next.newline_permitted = 1;
+ else if (indents)
+ next.newline_permitted = 0;
+ else if (p->tos)
+ next.newline_permitted =
+ p->stack[p->tos-1].newline_permitted;
+ else
+ next.newline_permitted = 0;
+
+ if (!start_of_line) {
+ if (p->tos)
+ next.since_newline = p->stack[p->tos-1].since_newline + 1;
+ else
+ next.since_newline = 1;
+ }
+ if (indents)
+ next.since_indent = 0;
+ else if (p->tos)
+ next.since_indent = p->stack[p->tos-1].since_indent + 1;
+ else
+ next.since_indent = 1;
+
+ p->stack[p->tos] = next;
p->asn_stack[p->tos] = asn;
p->tos++;
return 1;
`pop` primarily moves the top of stack (`tos`) back down the required
amount and frees any `asn` entries that need to be freed. It also
-collects a summary of the indents in the symbols that are being
-removed. It is called _after_ we reduce a production, just before we
-`shift` the nonterminal in.
-
-`pop` is only called if there are entries to remove, so `num` is never zero.
+collects a summary of the indents and line starts in the symbols that
+are being removed. It is called _after_ we reduce a production, just
+before we `shift` the nonterminal in.
###### parser functions
- static void pop(struct parser *p, int num, struct frame *next,
- void(*do_free)(short sym, void *asn))
+ static int pop(struct parser *p, int num,
+ short *start_of_line,
+ void(*do_free)(short sym, void *asn))
{
int i;
+ short indents = 0;
+ int sol = 0;
+
p->tos -= num;
- next->starts_indented =
- p->stack[p->tos].starts_indented;
- next->starts_newline =
- p->stack[p->tos].starts_newline;
- next->indents = 0;
for (i = 0; i < num; i++) {
- next->indents += p->stack[p->tos+i].indents;
+ sol |= !p->stack[p->tos+i].since_newline;
+ indents += p->stack[p->tos+i].indents;
do_free(p->stack[p->tos+i].sym,
p->asn_stack[p->tos+i]);
}
+ if (start_of_line)
+ *start_of_line = sol;
+ return indents;
}
### Memory allocation
### The heart of the parser.
-Now we have the parser. If we can shift, we do, though newlines and
-reducing indenting may block that. If not and we can reduce we do.
-If the production we reduced was production zero, then we have
+Now we have the parser. If we can shift we do, though newlines and
+reducing indenting may block that. If not and we can reduce we do
+that. If the production we reduced was production zero, then we have
accepted the input and can finish.
We return whatever `asn` was returned by reducing production zero.
to handle them directly as the grammar cannot express what we want to
do with them.
-`TK_in` tokens are easy: we simply update the `next` stack frame to
-record how many indents there are and that the next token started with
-an indent.
+`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 either be counted off against any pending indent,
-or must force reductions until there is a pending indent which isn't
-at the start of a production.
+`TK_out` tokens must either 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 extents 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.
-`TK_newline` tokens are ignored precisely if there has been an indent
-since the last state which could have been at the start of a line.
+`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 forcible
+terminates any line-like structure - we try to reduce down to at most
+one symbol for each line where newlines are allowed.
###### parser includes
#include "parser.h"
struct token_config *config)
{
struct parser p = { 0 };
- struct frame next = { 0 };
struct token *tk = NULL;
int accepted = 0;
void *ret = NULL;
- next.starts_newline = 1;
- shift(&p, &next, NULL, states);
+ shift(&p, TK_eof, 0, 1, NULL, states);
while (!accepted) {
struct token *err_tk;
struct frame *tos = &p.stack[p.tos-1];
if (!tk)
tk = tok_copy(token_next(tokens));
- next.sym = tk->num;
- parser_trace(trace, &p, &next, tk, states, non_term, config->known_count);
-
- if (next.sym == TK_in) {
- next.starts_indented = 1;
- next.indents = 1;
- next.starts_newline = 1;
+ parser_trace(trace, &p,
+ tk, states, non_term, config->known_count);
+
+ if (tk->num == TK_in) {
+ tos->indents += 1;
+ tos->since_newline = 0;
+ tos->since_indent = 0;
+ if (!states[tos->state].starts_line)
+ tos->newline_permitted = 0;
free(tk);
tk = NULL;
parser_trace_action(trace, "Record");
continue;
}
- if (next.sym == TK_out) {
- if (tos->indents > tos->starts_indented ||
- (tos->indents == 1 &&
- states[tos->state].reduce_size != 1)) {
- tos->indents -= 1;
- if (tos->indents <= tos->starts_indented) {
- // no internal indent any more, reassess 'newline_permitted'
- if (states[tos->state].starts_line)
- tos->newline_permitted = 1;
- else if (p.tos > 1)
- tos->newline_permitted = (p.stack[p.tos-2].newline_permitted ?:-1)+1;
+ if (tk->num == TK_out) {
+ if (states[tos->state].reduce_size >= 0 &&
+ states[tos->state].reduce_size <= tos->since_indent)
+ goto force_reduce;
+ if (states[tos->state].min_prefix >= tos->since_indent) {
+ // OK to cancel
+ struct frame *in = tos - tos->since_indent;
+ in->indents -= 1;
+ if (in->indents == 0) {
+ /* Reassess since_indent and newline_permitted */
+ if (in > p.stack) {
+ in->since_indent = in[-1].since_indent + 1;
+ in->newline_permitted = in[-1].newline_permitted;
+ } else {
+ in->since_indent = 0;
+ in->newline_permitted = 0;
+ }
+ if (states[in->state].starts_line)
+ in->newline_permitted = 1;
+ while (in < tos) {
+ in += 1;
+ in->since_indent = in[-1].since_indent + 1;
+ if (states[in->state].starts_line)
+ in->newline_permitted = 1;
+ else
+ in->newline_permitted = in[-1].newline_permitted;
+ }
}
free(tk);
tk = NULL;
// fall through and force a REDUCE (as 'shift'
// will fail).
}
- if (next.sym == TK_newline) {
- if (! tos->newline_permitted) {
+ if (tk->num == TK_newline) {
+ if (!tos->newline_permitted) {
free(tk);
tk = NULL;
parser_trace_action(trace, "Discard");
continue;
}
+ if (tos->since_newline > 1 &&
+ states[tos->state].reduce_size >= 0 &&
+ states[tos->state].reduce_size <= tos->since_newline)
+ goto force_reduce;
}
- if (shift(&p, &next, tk, states)) {
- next.starts_newline =
- tk->num == TK_newline;
- next.starts_indented = 0;
- next.indents = 0;
+ if (shift(&p, tk->num, 0, tk->num == TK_newline, tk, states)) {
tk = NULL;
parser_trace_action(trace, "Shift");
continue;
}
+ force_reduce:
if (states[tos->state].reduce_prod >= 0) {
void **body;
void *res;
int size = nextstate->reduce_size;
int bufsize;
static char buf[16*1024];
- struct frame frame;
- frame.sym = nextstate->reduce_sym;
+ short indents, start_of_line;
body = p.asn_stack + (p.tos - size);
bufsize = do_reduce(prod, body, config, buf);
- if (size)
- pop(&p, size, &frame, do_free);
- else {
- frame.indents = next.indents;
- frame.starts_indented = frame.indents;
- frame.starts_newline = 0;
- next.indents = 0;
- next.starts_indented = 0;
- }
+ indents = pop(&p, size, &start_of_line,
+ do_free);
res = memdup(buf, bufsize);
memset(buf, 0, bufsize);
- if (!shift(&p, &frame, res, states)) {
+ if (!shift(&p, nextstate->reduce_sym,
+ indents, start_of_line,
+ res, states)) {
if (prod != 0) abort();
accepted = 1;
ret = res;
if (tk->num == TK_out) {
// Indent problem - synthesise tokens to get us
// out of here.
- struct frame frame = { 0 };
fprintf(stderr, "Synthesize %d to handle indent problem\n", states[tos->state].shift_sym);
- frame.sym = states[tos->state].shift_sym;
- shift(&p, &frame, tok_copy(*tk), states);
+ 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;
* we find one that is acceptable.
*/
parser_trace_action(trace, "ERROR");
+ short indents = 0, start_of_line;
err_tk = tok_copy(*tk);
- next.sym = TK_error;
- while (shift(&p, &next, err_tk, states) == 0
+ while (shift(&p, TK_error, 0, 0,
+ err_tk, states) == 0
&& p.tos > 0)
// discard this state
- pop(&p, 1, &next, do_free);
+ indents += pop(&p, 1, &start_of_line, do_free);
if (p.tos == 0) {
free(err_tk);
// no state accepted TK_error
free(tk);
tk = tok_copy(token_next(tokens));
if (tk->num == TK_in)
- next.indents += 1;
+ indents += 1;
if (tk->num == TK_out) {
- if (next.indents == 0)
+ if (indents == 0)
break;
- next.indents -= 1;
+ indents -= 1;
+ // FIXME update since_indent here
}
}
if (p.tos == 0 && tk->num == TK_eof)
break;
+ tos = &p.stack[p.tos-1];
+ tos->indents += indents;
}
free(tk);
- if (p.tos)
- pop(&p, p.tos, &next, do_free);
+ pop(&p, p.tos, NULL, do_free);
free(p.asn_stack);
free(p.stack);
return ret;
if (states[f->state].starts_line)
fprintf(trace, "s");
if (f->newline_permitted)
- fprintf(trace, "n%d", f->newline_permitted);
+ fprintf(trace, "n%d", f->since_newline);
fprintf(trace, ") ");
}
- void parser_trace(FILE *trace, struct parser *p, struct frame *n,
+ void parser_trace(FILE *trace, struct parser *p,
struct token *tk, const struct state states[],
const char *non_term[], int knowns)
{
fputs(non_term[sym - TK_reserved - knowns],
trace);
if (f->indents)
- fprintf(trace, "%c%d", f->starts_indented?':':'.',
- f->indents);
- if (f->starts_newline)
+ fprintf(trace, ".%d", f->indents);
+ if (f->since_newline == 0)
fputs("/", trace);
fputs(" ", trace);
}
fputs(reserved_words[tk->num], trace);
else
text_dump(trace, tk->txt, 20);
- if (n->indents)
- fprintf(trace, "%c%d", n->starts_indented?':':'.',
- n->indents);
- if (n->starts_newline)
- fputs("/", trace);
fputs("]", trace);
}