项目文件夹

文件
wehub-resource-sync 41cb1c0170
CodeQL SAST / analyze (push) Failing after 1s
DCO / dco (push) Failing after 0s
Deploy Pages / deploy (push) Failing after 1s
OpenSSF Scorecard / scorecard (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

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);
}