deusdata--codebase-memory-mcp
1404 行
37 KiB
C
已生成的
1404 行
37 KiB
C
已生成的
/**
|
|
* 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);
|
|
} |