1404 lines
37 KiB
C
Generated
1404 lines
37 KiB
C
Generated
/**
|
||
* Print input and result information.
|
||
*/
|
||
// #define DEBUG 1
|
||
|
||
/**
|
||
* Print the upcoming token after parsing finished.
|
||
* Note: May change parser behaviour.
|
||
*/
|
||
// #define DEBUG_NEXT_TOKEN 1
|
||
|
||
#include "tree_sitter/parser.h"
|
||
#include <assert.h>
|
||
#ifdef DEBUG
|
||
#include <stdio.h>
|
||
#endif
|
||
#include <string.h>
|
||
#include <stdbool.h>
|
||
#include <wctype.h>
|
||
#include "unicode.h"
|
||
|
||
// short circuit
|
||
#define SHORT_SCANNER if (res.finished) return res;
|
||
#define PEEK state->lexer->lookahead
|
||
// Move the parser position one character to the right.
|
||
#define S_ADVANCE state->lexer->advance(state->lexer, false)
|
||
// Move the parser position one character to the right, treating the consumed character as whitespace.
|
||
#define S_SKIP state->lexer->advance(state->lexer, true)
|
||
#define SYM(s) (state->symbols[s])
|
||
|
||
#ifdef DEBUG
|
||
#define DEBUG_PRINTF(...) do{ fprintf( stderr, __VA_ARGS__ ); } while( false )
|
||
#else
|
||
#define DEBUG_PRINTF(...) do{ } while ( false )
|
||
#endif
|
||
|
||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||
|
||
#define VEC_RESIZE(vec, _cap) \
|
||
(vec)->data = realloc((vec)->data, (_cap) * sizeof((vec)->data[0])); \
|
||
assert((vec)->data != NULL); \
|
||
(vec)->cap = (_cap);
|
||
|
||
#define VEC_GROW(vec, _cap) if ((vec)->cap < (_cap)) { VEC_RESIZE((vec), (_cap)); }
|
||
|
||
#define VEC_PUSH(vec, el) \
|
||
if ((vec)->cap == (vec)->len) { VEC_RESIZE((vec), MAX(20, (vec)->len * 2)); } \
|
||
(vec)->data[(vec)->len++] = (el);
|
||
|
||
#define VEC_POP(vec) (vec)->len--;
|
||
|
||
#define VEC_NEW { .len = 0, .cap = 0, .data = NULL }
|
||
|
||
#define VEC_BACK(vec) ((vec)->data[(vec)->len - 1])
|
||
|
||
#define VEC_FREE(vec) { if ((vec)->data != NULL) free((vec)->data); }
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// Symbols
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* This enum is mapped to the `externals` list in the grammar according to how they are ordered (the names are
|
||
* abitrary).
|
||
*
|
||
* When the `scan` function is called, the parameter `syms` contains a bool for each enum attribute indicating whether
|
||
* the parse tree at the current position can accept the corresponding symbol.
|
||
*
|
||
* The attribute `fail` is not part of the parse tree, it is used to indicate that no matching symbol was found.
|
||
*
|
||
* The meanings are:
|
||
* - semicolon: An implicit end of a decl or statement, a newline in place of a semicolon
|
||
* - start: Start an implicit new layout after `where`, `do`, `of` or `in`, in place of an opening brace
|
||
* - end: End an implicit layout, in place of a closing brace
|
||
* - dot: For qualified modules `Data.List.null`, which have to be disambiguated from the `(.)` operator based on
|
||
* surrounding whitespace.
|
||
* - where: Parse an inline `where` token. This is necessary because `where` tokens can end layouts and it's necesary
|
||
* to know whether it is valid at that position, which can mean that it belongs to the last statement of the layout
|
||
* - comment: A line or block comment, because they interfere with operators, especially in QQs
|
||
* - comma: Needed to terminate inline layouts like `of`, `do`
|
||
* - bar: The vertical bar `|`, used for guards and list comprehension
|
||
* - in: Closes the layout of a `let` and consumes the token `in`
|
||
* - indent: Used as a dummy symbol for initialization; uses newline in the grammar to ensure the scanner is called
|
||
* for each token
|
||
* - empty: The empty file
|
||
* - fail: special indicator of failure
|
||
*/
|
||
typedef enum {
|
||
SEMICOLON,
|
||
START,
|
||
END,
|
||
DOT,
|
||
WHERE,
|
||
TYCONSYM,
|
||
COMMENT,
|
||
COMMA,
|
||
ATSIGN,
|
||
EQUALS,
|
||
BAR,
|
||
IN,
|
||
INDENT,
|
||
EMPTY,
|
||
FAIL,
|
||
} Sym;
|
||
|
||
#ifdef DEBUG
|
||
static char *sym_names[] = {
|
||
"semicolon",
|
||
"start",
|
||
"end",
|
||
"dot",
|
||
"where",
|
||
"comment",
|
||
"comma",
|
||
"atsign",
|
||
"equals",
|
||
"bar",
|
||
"in",
|
||
"indent",
|
||
"empty",
|
||
};
|
||
#endif
|
||
|
||
/**
|
||
* The parser appears to call `scan` with all symbols declared as valid directly after it encountered an error, so
|
||
* this function is used to detect them.
|
||
*/
|
||
static bool all_syms(const bool *syms) {
|
||
for (int i = 0; i <= EMPTY; i++) {
|
||
if (!syms[i]) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
/**
|
||
* Produce a comma-separated string of valid symbols.
|
||
*/
|
||
static void debug_valid(const bool *syms) {
|
||
if (all_syms(syms)) {
|
||
DEBUG_PRINTF("all");
|
||
return;
|
||
}
|
||
bool fst = true;
|
||
DEBUG_PRINTF("\"");
|
||
for (Sym i = SEMICOLON; i <= EMPTY; i++) {
|
||
if (syms[i]) {
|
||
if (!fst) DEBUG_PRINTF(",");
|
||
DEBUG_PRINTF("%s", sym_names[i]);
|
||
fst = false;
|
||
}
|
||
}
|
||
DEBUG_PRINTF("\"");
|
||
}
|
||
#endif
|
||
|
||
typedef struct {
|
||
uint32_t len;
|
||
uint32_t cap;
|
||
uint16_t *data;
|
||
} indent_vec;
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// State
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* This structure contains the external and internal state.
|
||
*
|
||
* The parser provides the lexer interface and the list of valid symbols.
|
||
*
|
||
* The internal state consists of a stack of indentation widths that is manipulated whenever a layout is started or
|
||
* terminated.
|
||
*/
|
||
typedef struct {
|
||
TSLexer *lexer;
|
||
const bool *symbols;
|
||
indent_vec *indents;
|
||
#ifdef DEBUG
|
||
int marked;
|
||
char *marked_by;
|
||
bool needs_free;
|
||
#endif
|
||
} State;
|
||
|
||
static State state_new(TSLexer *l, const bool * restrict vs, indent_vec *is) {
|
||
return (State) {
|
||
.lexer = l,
|
||
.symbols = vs,
|
||
.indents = is,
|
||
#ifdef DEBUG
|
||
.marked = -1,
|
||
.marked_by = "",
|
||
.needs_free = false,
|
||
#endif
|
||
};
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
static void debug_indents(indent_vec *indents) {
|
||
if (indents->len == 0) DEBUG_PRINTF("empty");
|
||
bool empty = true;
|
||
for (size_t i = 0; i < indents->len; i++) {
|
||
if (!empty) DEBUG_PRINTF("-");
|
||
DEBUG_PRINTF("%d", indents->data[i]);
|
||
empty = false;
|
||
}
|
||
}
|
||
|
||
void debug_state(State *state) {
|
||
DEBUG_PRINTF("State { syms = ");
|
||
debug_valid(state->symbols);
|
||
DEBUG_PRINTF(", indents = ");
|
||
debug_indents(state->indents);
|
||
DEBUG_PRINTF(" }\n");
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* These functions provide the basic interface to the lexer.
|
||
* They are not defined as members for easier composition.
|
||
*/
|
||
static bool is_eof(State *state) { return state->lexer->eof(state->lexer); }
|
||
|
||
/**
|
||
* The parser's position in the current line.
|
||
*/
|
||
static uint32_t column(State *state) {
|
||
return is_eof(state) ? 0 : state->lexer->get_column(state->lexer);
|
||
}
|
||
|
||
/**
|
||
* Instruct the lexer that the current position is the end of the potentially detected symbol, causing the next run to
|
||
* be started after this character in the success case.
|
||
*
|
||
* This is useful if the validity of the detected symbol depends on what follows, e.g. in the case of a layout end
|
||
* before a `where` token.
|
||
*/
|
||
|
||
// Only use string literals we actually need
|
||
#ifdef DEBUG
|
||
static void MARK(char *marked_by, bool needs_free, State *state) {
|
||
state->marked = column(state);
|
||
if (state->needs_free) free(state->marked_by);
|
||
state->marked_by = marked_by;
|
||
state->needs_free = needs_free;
|
||
state->lexer->mark_end(state->lexer);
|
||
}
|
||
#else
|
||
#define MARK(s, nf, state) state->lexer->mark_end(state->lexer);
|
||
#endif
|
||
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// Condition
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* The set of conditions used in the parser implementation.
|
||
*/
|
||
static bool varid_start_char(const uint32_t c) { return c == '_' || iswlower(c); }
|
||
|
||
static bool varid_char(const uint32_t c) {
|
||
switch (c) {
|
||
case '_':
|
||
case '\'':
|
||
return true;
|
||
default:
|
||
// TODO(414owen) is haskell C_LOCALE sensitive?
|
||
return iswalnum(c);
|
||
}
|
||
}
|
||
|
||
static bool seq(const char * restrict s, State *state) {
|
||
size_t len = strlen(s);
|
||
for (size_t i = 0; i < len; i++) {
|
||
int32_t c = s[i];
|
||
int32_t c2 = PEEK;
|
||
if (c != c2) return false;
|
||
S_ADVANCE;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static void consume_until(char *target, State *state) {
|
||
int32_t first = target[0];
|
||
assert(first != 0);
|
||
while (PEEK != 0 && !seq(target, state)) {
|
||
while (PEEK != 0 && PEEK != first) S_ADVANCE;
|
||
// TODO(414owen): This mimics the combinator's behaviour, but it seems a bit silly.
|
||
// Why mark where the first char matched? Let's just not do this check.
|
||
if (first == PEEK) {
|
||
#ifdef DEBUG
|
||
char *prefix = "consume_until ";
|
||
char *mark_target = calloc(strlen(prefix) + strlen(target) + 1, 1);
|
||
sprintf(mark_target, "%s%s", prefix, target);
|
||
MARK(mark_target, true, state);
|
||
#else
|
||
state->lexer->mark_end(state->lexer);
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
typedef struct {
|
||
uint32_t len;
|
||
uint32_t cap;
|
||
int32_t *data;
|
||
} wchar_vec;
|
||
|
||
static wchar_vec read_string(bool (*cond)(uint32_t), State *state) {
|
||
wchar_vec res = VEC_NEW;
|
||
int32_t c = PEEK;
|
||
while (cond(c)) {
|
||
VEC_PUSH(&res, c);
|
||
S_ADVANCE;
|
||
c = PEEK;
|
||
}
|
||
return res;
|
||
}
|
||
|
||
#define WS_CASES \
|
||
case ' ': \
|
||
case '\f': \
|
||
case '\n': \
|
||
case '\r': \
|
||
case '\t': \
|
||
case '\v'
|
||
|
||
/**
|
||
* Require that the next character is whitespace (space or newline) without advancing the parser.
|
||
*/
|
||
static bool isws(uint32_t c) {
|
||
switch (c) {
|
||
WS_CASES: return true;
|
||
default: return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* A token like a varsym can be terminated by whitespace or brackets.
|
||
*/
|
||
static bool token_end(uint32_t c) {
|
||
switch (c) {
|
||
WS_CASES:
|
||
case 0:
|
||
case '(':
|
||
case ')':
|
||
case '[':
|
||
case ']':
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Require that the argument string follows the current position and is followed by whitespace.
|
||
* See `seq`
|
||
*/
|
||
static bool token(const char *restrict s, State *state) {
|
||
return seq(s, state) && token_end(PEEK);
|
||
}
|
||
|
||
/**
|
||
* Require that the stack of layout indentations is not empty.
|
||
* This is mostly used for safety.
|
||
*/
|
||
static bool indent_exists(State *state) { return state->indents->len != 0; };
|
||
|
||
/**
|
||
* Require that the current line's indent is greater or equal than the containing layout's, so the current layout is
|
||
* continued.
|
||
*/
|
||
static bool keep_layout(uint16_t indent, State *state) {
|
||
return indent_exists(state) && indent >= VEC_BACK(state->indents);
|
||
}
|
||
|
||
/**
|
||
* Require that the current line's indent is equal to the containing layout's, so the line may start a new `decl`.
|
||
*/
|
||
static bool same_indent(uint32_t indent, State *state) { return indent_exists(state) && indent == VEC_BACK(state->indents); }
|
||
|
||
/**
|
||
* Require that the current line's indent is smaller than the containing layout's, so the layout may be ended.
|
||
*/
|
||
static bool smaller_indent(uint32_t indent, State *state) {
|
||
return indent_exists(state) && indent < VEC_BACK(state->indents);
|
||
}
|
||
|
||
static bool indent_lesseq(uint32_t indent, State *state) { return indent_exists(state) && indent <= VEC_BACK(state->indents); }
|
||
|
||
/**
|
||
* Composite condition examining whether the current layout can be terminated if the line after the position where the
|
||
* scan started begins with a `where`.
|
||
*
|
||
* This is needed because `where` can appear on the same indent as, for example, a `do` statement in a `decl`, while
|
||
* being part of the latter and therefore having to end the `do`'s layout before parsing the `where`.
|
||
*
|
||
* This does only check whether the line begins with a `w`, the entire `where` is consumed by the calling parser below.
|
||
*/
|
||
static bool is_newline_where(uint32_t indent, State *state) {
|
||
return keep_layout(indent, state)
|
||
&& (SYM(SEMICOLON) || SYM(END))
|
||
&& !SYM(WHERE)
|
||
&& PEEK == 'w';
|
||
}
|
||
|
||
#define NEWLINE_CASES \
|
||
case '\n': \
|
||
case '\r': \
|
||
case '\f'
|
||
|
||
static bool is_newline(uint32_t c) {
|
||
switch (c) {
|
||
NEWLINE_CASES:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Require that the state has not been initialized after parsing has started.
|
||
*
|
||
* This is necessary to handle a nonexistent `module` declaration.
|
||
*/
|
||
static bool uninitialized(State *state) { return !indent_exists(state); }
|
||
|
||
/**
|
||
* Require that the parser determined an error in the previous step (see `all_syms`).
|
||
*/
|
||
static bool after_error(State *state) { return all_syms(state->symbols); }
|
||
|
||
#define SYMBOLICS_WITHOUT_BAR \
|
||
case '!': \
|
||
case '#': \
|
||
case '$': \
|
||
case '%': \
|
||
case '&': \
|
||
case '*': \
|
||
case '+': \
|
||
case '.': \
|
||
case '/': \
|
||
case '<': \
|
||
case '>': \
|
||
case '?': \
|
||
case '^': \
|
||
case ':': \
|
||
case '=': \
|
||
case '-': \
|
||
case '~': \
|
||
case '@': \
|
||
case '\\'
|
||
|
||
#define SYMBOLIC_CASES \
|
||
SYMBOLICS_WITHOUT_BAR: \
|
||
case '|'
|
||
|
||
static bool symbolic(uint32_t c) {
|
||
switch (c) {
|
||
SYMBOLIC_CASES:
|
||
return true;
|
||
default:
|
||
return unicode_symbol(c);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Test for reserved operators of two characters.
|
||
*/
|
||
static bool valid_symop_two_chars(uint32_t first_char, uint32_t second_char) {
|
||
switch (first_char) {
|
||
case '-':
|
||
return second_char != '-' && second_char != '>';
|
||
case '=':
|
||
return second_char != '>';
|
||
case '<':
|
||
return second_char != '-';
|
||
case ':':
|
||
return second_char != ':';
|
||
default:
|
||
return true;
|
||
}
|
||
}
|
||
|
||
static bool valid_splice(State *state) {
|
||
return varid_start_char(PEEK) || PEEK == '(';
|
||
}
|
||
|
||
typedef enum {
|
||
S_OP,
|
||
S_STAR,
|
||
S_TILDE,
|
||
S_EQUALS,
|
||
S_ATSIGN,
|
||
S_IMPLICIT,
|
||
S_BAR,
|
||
S_COMMENT,
|
||
S_INVALID,
|
||
} Symbolic;
|
||
|
||
/**
|
||
* Symbolic operators that are eligible to close a layout when they are on a newline with less/eq indent.
|
||
*
|
||
* Very crude heuristic. Layouts bad. Very bad.
|
||
*/
|
||
static bool expression_op(Symbolic type) {
|
||
switch (type) {
|
||
case S_OP:
|
||
case S_STAR:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Check all conditions for symbolic expression operators and return a variant of the enum `Symbolic`.
|
||
*
|
||
* - The `single` predicate is used for single-character symops
|
||
* - does not match a reserved operator
|
||
* - is not a comment
|
||
*
|
||
* Even if one of those conditions is unmet, it might still be parsed as a varsym, e.g. if a strictness annotation is
|
||
* not valid at the current position.
|
||
*
|
||
* This only explicitly excludes `(!)` from being strictness; It could test for `varid` plus opening
|
||
* parens/bracket, but strictness is only valid in patterns and that makes it ambiguous anyway.
|
||
* Needs something better, but seems unlikely to be deterministic.
|
||
*
|
||
* Hashes followed by a varid start character `#foo` are labels.
|
||
*/
|
||
static Symbolic s_symop(wchar_vec s, State *state) {
|
||
if (s.data == NULL || s.data[0] == 0) return S_INVALID;
|
||
int32_t c = s.data[0];
|
||
if (s.len == 1) {
|
||
if (c == '#' && varid_start_char(PEEK)) return S_INVALID;
|
||
if (c == '?' && varid_start_char(PEEK)) return S_IMPLICIT;
|
||
if (c == '|') return S_BAR;
|
||
switch (c) {
|
||
case '*':
|
||
return S_STAR;
|
||
case '~':
|
||
return S_TILDE;
|
||
case '=':
|
||
return S_EQUALS;
|
||
case '@':
|
||
return S_ATSIGN;
|
||
case '.':
|
||
case '\\':
|
||
return S_INVALID;
|
||
default: return S_OP;
|
||
}
|
||
} else {
|
||
bool is_comment = (s.data[0] == '-') && (s.data[1] == '-');
|
||
if (is_comment) return S_COMMENT;
|
||
if (s.len == 2) {
|
||
if (!valid_symop_two_chars(s.data[0], s.data[1])) return S_INVALID;
|
||
}
|
||
}
|
||
return S_OP;
|
||
}
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// Result
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Returned by a parser, indicating whether to continue with the next parser (`finished`) which symbol to select when
|
||
* successful (`sym`).
|
||
*
|
||
* Whether parsing was successful is indicated by which symbol is selected – `FAIL` signals failure.
|
||
*/
|
||
typedef struct {
|
||
Sym sym;
|
||
bool finished;
|
||
} Result;
|
||
|
||
#ifdef DEBUG
|
||
void debug_result(Result res) {
|
||
DEBUG_PRINTF("Result { finished = %d", res.finished);
|
||
if (res.finished)
|
||
DEBUG_PRINTF(", result = %s }\n", sym_names[res.sym]);
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* Constructors for the continue, failure and success results.
|
||
*/
|
||
static Result res_cont = {.sym = FAIL, .finished = false};
|
||
static Result res_finish(Sym t) { return (Result) {.sym = t, .finished = true}; }
|
||
static Result res_fail = {.sym = FAIL, .finished = true};
|
||
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// Parser
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Parser that terminates the execution with the successful detection of the given symbol.
|
||
*/
|
||
static Result finish(const Sym s, char *restrict desc) {
|
||
DEBUG_PRINTF("finish: %s\n", desc);
|
||
return res_finish(s);
|
||
}
|
||
|
||
/**
|
||
* Parser that terminates the execution with the successful detection of the given symbol, but only if it is expected.
|
||
*/
|
||
static Result finish_if_valid(const Sym s, char *restrict desc, State *state) {
|
||
return SYM(s) ? finish(s, desc) : res_cont;
|
||
}
|
||
|
||
/**
|
||
* Add one level of indentation to the stack, caused by starting a layout.
|
||
*/
|
||
static void push(uint16_t ind, State *state) {
|
||
DEBUG_PRINTF("push: %d\n", ind);
|
||
VEC_PUSH(state->indents, ind);
|
||
}
|
||
|
||
/**
|
||
* Remove one level of indentation from the stack, caused by the end of a layout.
|
||
*/
|
||
static void pop(State *state) {
|
||
if (indent_exists(state)) {
|
||
DEBUG_PRINTF("pop\n");
|
||
VEC_POP(state->indents);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Advance the lexer until the following character is neither space nor tab.
|
||
*/
|
||
static void skipspace(State *state) {
|
||
for (;;) {
|
||
switch (PEEK) {
|
||
case ' ':
|
||
case '\t':
|
||
S_SKIP;
|
||
break;
|
||
default:
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* If a layout end is valid at this position, remove one indentation layer and succeed with layout end.
|
||
*/
|
||
static Result layout_end(char *desc, State *state) {
|
||
if (SYM(END)) {
|
||
pop(state);
|
||
return finish(END, desc);
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Convenience parser, since those two are often used together.
|
||
*/
|
||
static Result end_or_semicolon(char *desc, State *state) {
|
||
Result res = layout_end(desc, state);
|
||
SHORT_SCANNER;
|
||
return finish_if_valid(SEMICOLON, desc, state);
|
||
}
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// Logic
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* These parsers constitute the higher-level logic, loosely.
|
||
*/
|
||
|
||
|
||
/**
|
||
* Advance the parser until a non-whitespace character is encountered, while counting whitespace according to the rules
|
||
* in the syntax reference, resetting the counter on each newline.
|
||
*
|
||
* This advances to the first nonwhite character in the next nonempty line and determines its indentation.
|
||
*/
|
||
static uint32_t count_indent(State *state) {
|
||
uint32_t indent = 0;
|
||
for (;;) {
|
||
switch (PEEK) {
|
||
NEWLINE_CASES:
|
||
S_SKIP;
|
||
indent = 0;
|
||
break;
|
||
case ' ':
|
||
S_SKIP;
|
||
indent++;
|
||
break;
|
||
case '\t':
|
||
S_SKIP;
|
||
indent += 8;
|
||
break;
|
||
default:
|
||
return indent;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* End-of-file check.
|
||
*
|
||
* If EOF has been reached, two scenarios are valid:
|
||
* - The file is empty, in which case the parser is still at the root rule, where `S_EMPTY` is valid.
|
||
* - The current layout can be ended. This may happen multiple times, since the parser will restart until the last
|
||
* layout end rule has been parsed.
|
||
* If those cases do not apply, parsing fails.
|
||
*/
|
||
static Result eof(State *state) {
|
||
if (is_eof(state)) {
|
||
if (SYM(EMPTY)) {
|
||
return finish(EMPTY, "eof");
|
||
}
|
||
Result res = end_or_semicolon("eof", state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Set the initial indentation at the beginning of the file or module decl to the column of first nonwhite character,
|
||
* then succeed with the dummy symbol `INDENT`.
|
||
*
|
||
* If there is a `module` declaration, this will be handled by the grammar.
|
||
*/
|
||
static Result initialize(uint32_t column, State *state) {
|
||
if (uninitialized(state)) {
|
||
MARK("initialize", false, state);
|
||
bool match = token("module", state);
|
||
if (match) return res_fail;
|
||
push(column, state);
|
||
return finish(INDENT, "init");
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
static Result initialize_init(State *state) {
|
||
if (uninitialized(state)) {
|
||
uint32_t col = column(state);
|
||
if (col == 0) return initialize(col, state);
|
||
};
|
||
return res_cont;
|
||
}
|
||
|
||
static Result dot(State *state) {
|
||
if (SYM(DOT) && PEEK == '.') {
|
||
S_ADVANCE;
|
||
MARK("dot", false, state);
|
||
if (SYM(DOT)) return finish(DOT, "dot");
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* End a layout by removing an indentation from the stack, but only if the current column (which should be in the next
|
||
* line after skipping whitespace) is smaller than the layout indent.
|
||
*/
|
||
static Result dedent(uint32_t indent, State *state) {
|
||
if (smaller_indent(indent, state)) return layout_end("dedent", state);
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Succeed if a `where` on a newline can end a statement or layout (see `is_newline_where`).
|
||
*
|
||
* This is the case after `do` or `of`, where the `where` can be on the same indent.
|
||
*/
|
||
static Result newline_where(uint32_t indent, State *state) {
|
||
if (is_newline_where(indent, state)) {
|
||
MARK("newline_where", false, state);
|
||
if (token("where", state)) {
|
||
return end_or_semicolon("newline_where", state);
|
||
}
|
||
return res_fail;
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Succeed for `SEMICOLON` if the indent of the next line is equal to the current layout's.
|
||
*/
|
||
static Result newline_semicolon(uint32_t indent, State *state) {
|
||
if (SYM(SEMICOLON) && same_indent(indent, state)) {
|
||
return finish(SEMICOLON, "newline_semicolon");
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* A layout may be closed by an infix operator on the same column as a `do` layout:
|
||
*
|
||
* a :: IO Int
|
||
* a = do a <- pure 5
|
||
* pure a
|
||
* >>= pure
|
||
*
|
||
* In this situation, the entire `do` block is the left operand of the `>>=`.
|
||
* The same applies for `infix` functions.
|
||
*/
|
||
static bool end_on_infix(uint32_t indent, Symbolic type, State *state) {
|
||
return indent_lesseq(indent, state) && (expression_op(type) || PEEK == '`');
|
||
}
|
||
|
||
/**
|
||
* End a layout if the next token is an infix operator and the indent is equal to or less than the current layout.
|
||
*/
|
||
static Result newline_infix(uint32_t indent, Symbolic type, State *state) {
|
||
if (end_on_infix(indent, type, state)) {
|
||
return layout_end("newline_infix", state);
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Parse an inline `where` token.
|
||
*
|
||
* Necessary because `is_newline_where` needs to know that no `where` may follow.
|
||
*/
|
||
static Result where(State *state) {
|
||
if (token("where", state)) {
|
||
if (SYM(WHERE)) {
|
||
MARK("where", false, state);
|
||
return finish(WHERE, "where");
|
||
}
|
||
return layout_end("where", state);
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* An `in` token ends the layout openend by a `let` and its nested layouts.
|
||
*/
|
||
static Result in(State *state) {
|
||
if (SYM(IN) && token("in", state)) {
|
||
MARK("in", false, state);
|
||
pop(state);
|
||
return finish(IN, "in");
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* An `else` token may end a layout opened in the body of a `then`.
|
||
*/
|
||
static Result else_(State *state) {
|
||
return !token("else instance", state) && token("else", state) ? end_or_semicolon("else", state) : res_cont;
|
||
}
|
||
|
||
|
||
/**
|
||
* Consume all characters up to the end of line and succeed with `syms::commment`.
|
||
*/
|
||
static Result inline_comment(State *state) {
|
||
for (;;) {
|
||
switch (PEEK) {
|
||
NEWLINE_CASES:
|
||
case 0:
|
||
goto inline_comment_after_skip;
|
||
default:
|
||
S_ADVANCE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
inline_comment_after_skip:
|
||
MARK("inline_comment", false, state);
|
||
return finish(COMMENT, "inline_comment");
|
||
}
|
||
|
||
/**
|
||
* Parse a sequence of symbolic characters and convert it into the enum `Symbolic`.
|
||
* This decides whether the sequence is an operator or a special case.
|
||
*/
|
||
static Symbolic read_symop(State *state) {
|
||
wchar_vec s = read_string(symbolic, state);
|
||
Symbolic res = s_symop(s, state);
|
||
free(s.data);
|
||
return res;
|
||
}
|
||
|
||
|
||
static Result symop_marked(Symbolic type, State *state) {
|
||
switch (type) {
|
||
case S_INVALID:
|
||
return res_fail;
|
||
case S_STAR:
|
||
case S_TILDE:
|
||
case S_IMPLICIT:
|
||
return res_fail;
|
||
case S_ATSIGN:
|
||
return finish(ATSIGN, "atsign");
|
||
case S_EQUALS:
|
||
return finish(EQUALS, "equals");
|
||
case S_COMMENT:
|
||
return inline_comment(state);
|
||
default:
|
||
return res_cont;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Map a `Symbolic` variant to the appropriate symbol, focusing on operators and their edge cases.
|
||
*
|
||
* - Star, tilde and minus are only valid as type operators
|
||
* - Implicit `?` with immediate varid is always invalid, to be parsed by the grammar
|
||
*
|
||
*/
|
||
|
||
static Result symop(Symbolic type, State *state) {
|
||
if (type == S_BAR) {
|
||
if (SYM(BAR)) {
|
||
MARK("bar", false, state);
|
||
return finish(BAR, "bar");
|
||
}
|
||
Result res = layout_end("bar", state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
MARK("symop", false, state);
|
||
Result res = symop_marked(type, state);
|
||
SHORT_SCANNER;
|
||
// res = finish_if_valid(TYCONSYM, "symop", state);
|
||
// SHORT_SCANNER;
|
||
// res = finish_if_valid(VARSYM, "symop", state);
|
||
// SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
|
||
/**
|
||
* Parse an inline comment if the next chars are two or more minuses and the char after the last minus is not
|
||
* symbolic.
|
||
*
|
||
* To be called when it is certain that two minuses cannot succeed as a symbolic operator.
|
||
* Those cases are:
|
||
* - `START` is valid
|
||
* - Operator matching was done already
|
||
*/
|
||
static Result minus(State *state) {
|
||
if (!seq("--", state)) return res_cont;
|
||
while (PEEK == '-') S_ADVANCE;
|
||
if (symbolic(PEEK)) return res_fail;
|
||
return inline_comment(state);
|
||
}
|
||
|
||
/**
|
||
* Succeed for a comment.
|
||
*/
|
||
static Result multiline_comment_success(State *state) {
|
||
MARK("multiline_comment", false, state);
|
||
return finish(COMMENT, "multiline_comment");
|
||
}
|
||
|
||
/**
|
||
* See `nested_comment`.
|
||
*
|
||
* Since {- -} comments cannot be nested arbitrarily (unlike Haskell), we can get very greedy.
|
||
*/
|
||
static Result multiline_comment(State *state) {
|
||
for (;;) {
|
||
switch (PEEK) {
|
||
case '-':
|
||
S_ADVANCE;
|
||
if (PEEK == '}') {
|
||
S_ADVANCE;
|
||
return multiline_comment_success(state);
|
||
}
|
||
break;
|
||
case 0: {
|
||
Result res = eof(state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
default:
|
||
S_ADVANCE;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* When a brace is encountered, it can be an explicitly started layout, or a comment. In the latter case, the
|
||
* comment is parsed, otherwise parsing fails to delegate to the corresponding grammar rule.
|
||
*/
|
||
static Result brace(State *state) {
|
||
if (PEEK != '{') return res_fail;
|
||
S_ADVANCE;
|
||
if (PEEK != '-') return res_fail;
|
||
S_ADVANCE;
|
||
return multiline_comment(state);
|
||
}
|
||
|
||
/**
|
||
* Parse either inline or block comments.
|
||
*/
|
||
static Result comment(State *state) {
|
||
switch (PEEK) {
|
||
case '-': {
|
||
Result res = minus(state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
case '{': {
|
||
Result res = brace(state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* `case` can open a layout in a list:
|
||
*
|
||
* [case a of a -> a, case a of a -> a]
|
||
* [case a of a -> a | a <- a]
|
||
*
|
||
* Commas, vertical bars and closing brackets are able to close those.
|
||
*
|
||
* Because commas can also occur in class layouts at the top level, e.g. in fixity decls, the comma rule has to be
|
||
* parsed here as well.
|
||
*/
|
||
static Result close_layout_in_list(State *state) {
|
||
switch (PEEK) {
|
||
case ']': {
|
||
if (state->symbols[END]) {
|
||
pop(state);
|
||
return finish(END, "bracket");
|
||
}
|
||
break;
|
||
}
|
||
case ',': {
|
||
S_ADVANCE;
|
||
if (state->symbols[COMMA]) {
|
||
MARK("comma", false, state);
|
||
return finish(COMMA, "comma");
|
||
}
|
||
Result res = layout_end("comma", state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Parse special tokens before the first newline that can't be reliably detected by tree-sitter:
|
||
*
|
||
* - `where` here is just for the actual valid token
|
||
* - `in` closes a layout when inline
|
||
* - `)` can end the layout of an `of`
|
||
* - symbolic operators are complicated to implement with regex
|
||
*/
|
||
static Result inline_tokens(State *state) {
|
||
uint32_t c = PEEK;
|
||
bool is_symbolic = false;
|
||
switch (c) {
|
||
case 'w': {
|
||
Result res = where(state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
case 'i': {
|
||
Result res = in(state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
case 'e': {
|
||
Result res = else_(state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
case ')': {
|
||
Result res = layout_end(")", state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
// TODO(414owen) does this clash with inline comments '--'?
|
||
// I'm not sure why there's a `symbolic::comment` and a `COMMENT`...
|
||
SYMBOLICS_WITHOUT_BAR: {
|
||
is_symbolic = true;
|
||
}
|
||
// '-' case covered by symop
|
||
case '{': {
|
||
Result res = comment(state);
|
||
SHORT_SCANNER;
|
||
}
|
||
}
|
||
if (is_symbolic || unicode_symbol(c)) {
|
||
Symbolic s = read_symop(state);
|
||
return symop(s, state);
|
||
}
|
||
return close_layout_in_list(state);
|
||
}
|
||
|
||
/**
|
||
* If the symbol `START` is valid, starting a new layout is almost always indicated.
|
||
*
|
||
* If the next character is a left brace, it is either a comment, pragma or an explicit layout. In the comment case, the
|
||
* it must be parsed here.
|
||
* If the next character is a minus, it might be a comment.
|
||
*
|
||
* In all of those cases, the layout can't be started now. In the comment and pragma case, it will be started in the
|
||
* next run.
|
||
*
|
||
* This pushes the indentation of the first non-whitespace character onto the stack.
|
||
*/
|
||
static Result layout_start(uint32_t column, State *state) {
|
||
if (state->symbols[START]) {
|
||
switch (PEEK) {
|
||
case '-': {
|
||
Result res = minus(state);
|
||
SHORT_SCANNER;
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
push(column, state);
|
||
return finish(START, "layout_start");
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* After a layout has ended, the originator might need to be terminated by semicolon as well, but since the layout end
|
||
* advances until the next line, it cannot be done in the newline checks.
|
||
*
|
||
* This can happen, for example, with nested `do` layouts:
|
||
*
|
||
* f = do
|
||
* a <- b
|
||
* do c <- d
|
||
* e
|
||
* f
|
||
*
|
||
* Here, when the inner `do`'s layout is ended, the next step is started at `f`, but the outer `do`'s layout expects a
|
||
* semicolon. Since `f` is on the same indent as the outer `do`'s layout, this parser matches.
|
||
*/
|
||
static Result post_end_semicolon(uint32_t column, State *state) {
|
||
return SYM(SEMICOLON) && indent_lesseq(column, state)
|
||
? finish(SEMICOLON, "post_end_semicolon")
|
||
: res_cont;
|
||
}
|
||
|
||
/**
|
||
* Like `post_end_semicolon`, but for layout end.
|
||
*/
|
||
static Result repeat_end(uint32_t column, State *state) {
|
||
if (state->symbols[END] && smaller_indent(column, state)) {
|
||
return layout_end("repeat_end", state);
|
||
}
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* Rules that decide based on the indent of the next line.
|
||
*/
|
||
static Result newline_indent(uint32_t indent, State *state) {
|
||
Result res = dedent(indent, state);
|
||
SHORT_SCANNER;
|
||
res = close_layout_in_list(state);
|
||
SHORT_SCANNER;
|
||
return newline_semicolon(indent, state);
|
||
}
|
||
|
||
/**
|
||
* Rules that decide based on the first token on the next line.
|
||
*/
|
||
static Result newline_token(uint32_t indent, State *state) {
|
||
uint32_t c = PEEK;
|
||
bool is_symbolic = false;
|
||
switch (c) {
|
||
SYMBOLIC_CASES:
|
||
case '`': {
|
||
is_symbolic = true;
|
||
}
|
||
}
|
||
if (is_symbolic || unicode_symbol(c)) {
|
||
Symbolic s = read_symop(state);
|
||
Result res = newline_infix(indent, s, state);
|
||
SHORT_SCANNER;
|
||
return res_fail;
|
||
}
|
||
Result res = newline_where(indent, state);
|
||
SHORT_SCANNER;
|
||
if (PEEK == 'i') return in(state);
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* To be called after parsing a newline, with the indent of the next line as argument.
|
||
*/
|
||
static Result newline(uint32_t indent, State *state) {
|
||
Result res = eof(state);
|
||
SHORT_SCANNER;
|
||
res = initialize(indent, state);
|
||
SHORT_SCANNER;
|
||
res = comment(state);
|
||
SHORT_SCANNER;
|
||
res = newline_token(indent, state);
|
||
SHORT_SCANNER;
|
||
return newline_indent(indent, state);
|
||
}
|
||
|
||
/**
|
||
* Parsers that have to run when the next non-space character is not a newline:
|
||
*
|
||
* - Layout start
|
||
* - ending nested layouts at the same position
|
||
* - symbolic operators
|
||
* - Tokens `where`, `in`, `$`, `)`, `]`, `,`
|
||
* - comments
|
||
*/
|
||
static Result immediate(uint32_t column, State *state) {
|
||
Result res = layout_start(column, state);
|
||
SHORT_SCANNER;
|
||
res = post_end_semicolon(column, state);
|
||
SHORT_SCANNER;
|
||
res = repeat_end(column, state);
|
||
SHORT_SCANNER;
|
||
return inline_tokens(state);
|
||
}
|
||
|
||
/**
|
||
* Parsers that have to run _before_ parsing whitespace:
|
||
*
|
||
* - Error check
|
||
* - Indent stack initialization
|
||
* - Qualified module dot (leading whitespace would mean it would be `(.)`)
|
||
*/
|
||
static Result init(State *state) {
|
||
Result res = eof(state);
|
||
SHORT_SCANNER;
|
||
res = after_error(state) ? res_fail : res_cont;
|
||
SHORT_SCANNER;
|
||
res = initialize_init(state);
|
||
SHORT_SCANNER;
|
||
res = dot(state);
|
||
SHORT_SCANNER;
|
||
return res_cont;
|
||
}
|
||
|
||
/**
|
||
* The main parser checks whether the first non-space character is a newline and delegates accordingly.
|
||
*/
|
||
static Result scan_main(State *state) {
|
||
skipspace(state);
|
||
Result res = eof(state);
|
||
SHORT_SCANNER;
|
||
MARK("main", false, state);
|
||
if (is_newline(PEEK)) {
|
||
S_SKIP;
|
||
uint32_t indent = count_indent(state);
|
||
return newline(indent, state);
|
||
}
|
||
uint32_t col = column(state);
|
||
return immediate(col, state);
|
||
}
|
||
|
||
/**
|
||
* The entry point to the parser.
|
||
*/
|
||
static Result scan_all(State *state) {
|
||
Result res = init(state);
|
||
SHORT_SCANNER;
|
||
return scan_main(state);
|
||
}
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// Evaluation
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Helper that consume_if all characters up to the next whitespace, for debugging after a run.
|
||
*
|
||
* Note: This may break the parser, since not all paths use `mark`.
|
||
*/
|
||
#ifdef DEBUG
|
||
static void debug_lookahead(State *state) {
|
||
bool first = true;
|
||
for (;;) {
|
||
if (isws(PEEK) || PEEK == 0) break;
|
||
else {
|
||
if (first) DEBUG_PRINTF("next: ");
|
||
DEBUG_PRINTF("%c\n", PEEK);
|
||
S_ADVANCE;
|
||
first = false;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
|
||
/**
|
||
* The main function of the parsing machinery, executing the parser by passing in the initial state and analyzing the
|
||
* result.
|
||
*
|
||
* If the parser concluded with success, the `result_symbol` attribute of the lexer is set, by which the parsed symbol
|
||
* is communicated to tree-sitter, and `true` is returned, indicating to tree-sitter to use the result.
|
||
*
|
||
* If the parser concluded with failure, no `result_symbol` is set and `false` is returned.
|
||
*
|
||
* If the parser did _not_ conclude, i.e. all steps finished with `cont`, a failure is reported as well.
|
||
*
|
||
* If the `DEBUG_NEXT_TOKEN` flag is set, the next token will be printed.
|
||
*/
|
||
static bool eval(Result (*chk)(State *state), State *state) {
|
||
Result result = chk(state);
|
||
#ifdef DEBUG_NEXT_TOKEN
|
||
debug_lookahead(state);
|
||
#endif
|
||
if (result.finished && result.sym != FAIL) {
|
||
#ifdef DEBUG
|
||
// TODO(414owen) can names[] fail?
|
||
DEBUG_PRINTF("result: %s, ", sym_names[result.sym]);
|
||
if (state->marked == -1) {
|
||
DEBUG_PRINTF("%d\n", column(state));
|
||
} else {
|
||
DEBUG_PRINTF("%s@%d\n", state->marked_by, state->marked);
|
||
}
|
||
#endif
|
||
state->lexer->result_symbol = result.sym;
|
||
return true;
|
||
} else return false;
|
||
}
|
||
|
||
// --------------------------------------------------------------------------------------------------------
|
||
// API
|
||
// --------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* This function allocates the persistent state of the parser that is passed into the other API functions.
|
||
*/
|
||
void *tree_sitter_purescript_external_scanner_create() {
|
||
void *res = calloc(sizeof(indent_vec), 1);
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* Main logic entry point.
|
||
* Since the state is a singular vector, it can just be cast and used directly.
|
||
*/
|
||
bool tree_sitter_purescript_external_scanner_scan(void *indents_v, TSLexer *lexer, const bool *syms) {
|
||
indent_vec *indents = (indent_vec*) indents_v;
|
||
State state = {
|
||
.lexer = lexer,
|
||
.symbols = syms,
|
||
.indents = indents
|
||
};
|
||
#ifdef DEBUG
|
||
debug_state(&state);
|
||
if (state.needs_free) free(state.marked_by);
|
||
#endif
|
||
return eval(scan_all, &state);
|
||
}
|
||
|
||
/**
|
||
* Copy the current state to another location for later reuse.
|
||
* This is normally more complex, but since this parser's state constists solely of a vector of integers, it can just be
|
||
* copied.
|
||
*/
|
||
unsigned tree_sitter_purescript_external_scanner_serialize(void *indents_v, char *buffer) {
|
||
indent_vec *indents = (indent_vec*) indents_v;
|
||
unsigned to_copy = sizeof(indents->data[0]) * indents->len;
|
||
if (to_copy > TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
|
||
return 0;
|
||
}
|
||
if (to_copy > 0)
|
||
memcpy(buffer, indents->data, to_copy);
|
||
return to_copy;
|
||
}
|
||
|
||
/**
|
||
* Load another parser state into the currently active state.
|
||
* `payload` is the state of the previous parser execution, while `buffer` is the saved state of a different position
|
||
* (e.g. when doing incremental parsing).
|
||
*/
|
||
void tree_sitter_purescript_external_scanner_deserialize(void *indents_v, const char *buffer, unsigned length) {
|
||
indent_vec *indents = (indent_vec*) indents_v;
|
||
unsigned els = length / sizeof(indents->data[0]);
|
||
if (els > 0) {
|
||
VEC_GROW(indents, els);
|
||
indents->len = els;
|
||
memcpy(indents->data, buffer, length);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Destroy the state.
|
||
*/
|
||
void tree_sitter_purescript_external_scanner_destroy(void *indents_v) {
|
||
indent_vec *indents = (indent_vec*) indents_v;
|
||
VEC_FREE(indents);
|
||
free(indents);
|
||
} |