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

1404 lines
37 KiB
C
Generated
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
}