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

2038 lines
75 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.
#include "tree_sitter/parser.h"
#include "tree_sitter/alloc.h"
#include "tree_sitter/array.h"
#include "assert.h"
#include "limits.h"
#include "string.h"
#include "wctype.h"
/**
* Macro; goes to the lexer state without consuming any codepoints.
*
* @param state_value The new lexer state.
*/
#define GO_TO_STATE(state_value) \
{ \
state = state_value; \
goto start; \
}
/**
* Macro; marks the given lexeme as accepted.
*
* @param lexeme The lexeme to mark as accepted.
*/
#define ACCEPT_LEXEME(lexeme) \
{ \
(void)result; \
result_lexeme = lexeme; \
}
/**
* Macro; marks the given token as accepted without also marking the
* current position as its end.
*
* @param token The token to mark as accepted.
*/
#define ACCEPT_LOOKAHEAD_TOKEN(token) \
{ \
result = true; \
lexer->result_symbol = token; \
}
/**
* Macro; ends a lexer state by returning any accepted lexeme.
*/
#define END_LEX_STATE() \
{ \
return result_lexeme; \
}
// Tokens emitted by this external scanner.
enum TokenType {
LEADING_EXTRAMODULAR_TEXT, // Freeform text at the start of the file.
TRAILING_EXTRAMODULAR_TEXT, // Freeform text between or after modules.
INDENT, // Marks beginning of junction list.
BULLET, // New item of a junction list.
DEDENT, // Marks end of junction list.
BEGIN_PROOF, // Marks the beginning of an entire proof.
BEGIN_PROOF_STEP, // Marks the beginning of a proof step.
PROOF_KEYWORD, // The PROOF keyword.
BY_KEYWORD, // The BY keyword.
OBVIOUS_KEYWORD, // The OBVIOUS keyword.
OMITTED_KEYWORD, // The OMITTED keyword.
QED_KEYWORD, // The QED keyword.
WEAK_FAIRNESS, // The WF_ keyword.
STRONG_FAIRNESS, // The SF_ keyword.
PCAL_START, // Notifies scanner of start of PlusCal block.
PCAL_END, // Notifies scanner of end of PlusCal block.
DOUBLE_EXCL, // The !! infix op; lexical conflict with subexpr!
ERROR_SENTINEL // Only valid if in error recovery mode.
};
// Datatype used to record length of nested proofs & jlists.
typedef int16_t nest_address;
// Datatype used to record column index of jlists.
typedef int16_t column_index;
// Datatype used to record proof levels.
typedef int32_t proof_level;
// A dynamic array of chars.
typedef Array(char) CharArray;
/**
* Advances the scanner while marking the codepoint as non-whitespace.
*
* @param lexer The tree-sitter lexing control structure.
*/
static void advance(TSLexer* const lexer) {
lexer->advance(lexer, false);
}
/**
* Checks whether the next codepoint is the one given.
*
* @param lexer The tree-sitter lexing control structure.
* @param codepoint The codepoint to check.
* @return Whether the next codepoint is the one given.
*/
static bool is_next_codepoint(
const TSLexer* const lexer,
int32_t const codepoint
) {
return codepoint == lexer->lookahead;
}
/**
* Checks whether there are any codepoints left in the string.
*
* @param lexer The tree-sitter lexing control structure.
* @return Whether there are any codepoints left in the string.
*/
static bool has_next(const TSLexer* const lexer) {
return !lexer->eof(lexer);
}
/**
* Checks whether the given codepoint could be used in an identifier,
* which consist of capital ASCII letters, lowercase ASCII letters,
* and underscores.
*
* @param codepoint The codepoint to check.
* @return Whether the given codepoint could be used in an identifier.
*/
static bool is_identifier_char(int32_t const codepoint) {
return iswalnum(codepoint) || ('_' == codepoint);
}
/**
* Consumes codepoints as long as they are the one given.
*
* @param lexer The tree-sitter lexing control structure.
* @param codepoint The codepoint to consume.
* @return The number of codepoints consumed.
*/
static void consume_codepoint(TSLexer* const lexer, const int32_t codepoint) {
while (has_next(lexer) && is_next_codepoint(lexer, codepoint)) {
advance(lexer);
}
}
/**
* Checks whether the next codepoint sequence is the one given.
* This function can change the state of the lexer.
*
* @param lexer The tree-sitter lexing control structure.
* @param token The codepoint sequence to check for.
* @return Whether the next codepoint sequence is the one given.
*/
static bool is_next_codepoint_sequence(
TSLexer* const lexer,
const char codepoint_sequence[]
) {
size_t sequence_length = strlen(codepoint_sequence);
for (size_t i = 0; i < sequence_length; i++) {
int32_t codepoint = codepoint_sequence[i];
if (!is_next_codepoint(lexer, codepoint)) {
return false;
} else if (i + 1 < sequence_length) {
advance(lexer);
}
}
return true;
}
// Possible states for the extramodular text lexer to enter.
enum EMTLexState {
EMTLexState_CONSUME,
EMTLexState_DASH,
EMTLexState_SINGLE_LINE,
EMTLexState_MODULE,
EMTLexState_BLANK_BEFORE_MODULE,
EMTLexState_END_OF_FILE,
EMTLexState_BLANK_BEFORE_END_OF_FILE
};
/**
* Scans for extramodular text, the freeform text that can be present
* outside of TLA⁺ modules. This function skips any leading whitespace
* to avoid extraneous extramodular text tokens given newlines at the
* beginning or end of the file. It will consume any text up to the
* point it performs lookahead that captures the following regex:
* /----[-]*[ ]*MODULE/
* or EOF, which marks the end of the extramodular text. It is important
* that the extramodular text does not itself include the captured module
* start sequence, which is why this is in an external scanner rather
* than a regex in the grammar itself.
*
* @param lexer The tree-sitter lexing control structure
* @param valid_symbols Tokens possibly expected in this spot.
* @return Whether any extramodular text was detected.
*/
static bool scan_extramodular_text(TSLexer* const lexer, const bool* const valid_symbols) {
bool has_consumed_any = false;
enum EMTLexState state = EMTLexState_CONSUME;
START_LEXER();
eof = !has_next(lexer);
switch (state) {
case EMTLexState_CONSUME:
if (eof) ADVANCE(EMTLexState_END_OF_FILE);
if (iswspace(lookahead) && !has_consumed_any) SKIP(EMTLexState_CONSUME);
if (iswspace(lookahead) && has_consumed_any) ADVANCE(EMTLexState_CONSUME);
lexer->mark_end(lexer);
if ('-' == lookahead) ADVANCE(EMTLexState_DASH);
has_consumed_any = true;
ADVANCE(EMTLexState_CONSUME);
END_STATE();
case EMTLexState_DASH:
if (is_next_codepoint_sequence(lexer, "---")) ADVANCE(EMTLexState_SINGLE_LINE);
has_consumed_any = true;
GO_TO_STATE(EMTLexState_CONSUME);
END_STATE();
case EMTLexState_SINGLE_LINE:
consume_codepoint(lexer, '-');
consume_codepoint(lexer, ' ');
if (is_next_codepoint_sequence(lexer, "MODULE")) { ADVANCE(EMTLexState_MODULE); }
has_consumed_any = true;
GO_TO_STATE(EMTLexState_CONSUME);
END_STATE();
case EMTLexState_MODULE:
if (!has_consumed_any) GO_TO_STATE(EMTLexState_BLANK_BEFORE_MODULE);
ACCEPT_LOOKAHEAD_TOKEN(valid_symbols[LEADING_EXTRAMODULAR_TEXT] ? LEADING_EXTRAMODULAR_TEXT : TRAILING_EXTRAMODULAR_TEXT);
END_STATE();
case EMTLexState_BLANK_BEFORE_MODULE:
END_STATE();
case EMTLexState_END_OF_FILE:
if (!has_consumed_any) GO_TO_STATE(EMTLexState_BLANK_BEFORE_END_OF_FILE);
if (valid_symbols[TRAILING_EXTRAMODULAR_TEXT]) { ACCEPT_TOKEN(TRAILING_EXTRAMODULAR_TEXT); }
END_STATE();
case EMTLexState_BLANK_BEFORE_END_OF_FILE:
END_STATE();
default:
END_STATE();
}
}
// Types of proof step IDs.
enum ProofStepIdType {
ProofStepIdType_STAR, // <*>
ProofStepIdType_PLUS, // <+>
ProofStepIdType_NUMBERED, // <1234>
ProofStepIdType_NONE // Invalid or nonexistent
};
// Data about a proof step ID.
struct ProofStepId {
// The proof step ID type.
enum ProofStepIdType type;
// The proof step ID level (-1 if not NUMBERED).
proof_level level;
};
/**
* Initializes a new instance of the ProofStepId class.
*
* @param raw_level The unparsed contents of the <...> lexeme.
*/
static struct ProofStepId parse_proof_step_id(const CharArray* raw_level) {
struct ProofStepId id;
id.level = -1;
if (0 == raw_level->size) {
id.type = ProofStepIdType_NONE;
} else if ('*' == *array_get(raw_level, 0)) {
id.type = ProofStepIdType_STAR;
} else if ('+' == *array_get(raw_level, 0)) {
id.type = ProofStepIdType_PLUS;
} else {
id.type = ProofStepIdType_NUMBERED;
id.level = 0;
int32_t multiplier = 1;
for (size_t i = 0; i < raw_level->size; i++) {
const size_t index = raw_level->size - i - 1;
const int8_t digit_value = *array_get(raw_level, index) - 48;
if (0 <= digit_value && digit_value <= 9) {
id.level += digit_value * multiplier;
multiplier *= 10;
} else {
id.type = ProofStepIdType_NONE;
id.level = -1;
break;
}
}
}
return id;
}
// Lexemes recognized by this lexer.
enum Lexeme {
Lexeme_FORWARD_SLASH,
Lexeme_BACKWARD_SLASH,
Lexeme_GT,
Lexeme_EQ,
Lexeme_DASH,
Lexeme_COMMA,
Lexeme_COLON,
Lexeme_SEMICOLON,
Lexeme_LAND,
Lexeme_LOR,
Lexeme_DOUBLE_EXCL,
Lexeme_L_PAREN,
Lexeme_R_PAREN,
Lexeme_R_SQUARE_BRACKET,
Lexeme_R_CURLY_BRACE,
Lexeme_R_ANGLE_BRACKET,
Lexeme_RIGHT_ARROW,
Lexeme_RIGHT_MAP_ARROW,
Lexeme_COMMENT_START,
Lexeme_BLOCK_COMMENT_START,
Lexeme_SINGLE_LINE,
Lexeme_DOUBLE_LINE,
Lexeme_ASSUME_KEYWORD,
Lexeme_ASSUMPTION_KEYWORD,
Lexeme_AXIOM_KEYWORD,
Lexeme_BY_KEYWORD,
Lexeme_CONSTANT_KEYWORD,
Lexeme_CONSTANTS_KEYWORD,
Lexeme_COROLLARY_KEYWORD,
Lexeme_ELSE_KEYWORD,
Lexeme_IN_KEYWORD,
Lexeme_LEMMA_KEYWORD,
Lexeme_LOCAL_KEYWORD,
Lexeme_OBVIOUS_KEYWORD,
Lexeme_OMITTED_KEYWORD,
Lexeme_PROOF_KEYWORD,
Lexeme_PROPOSITION_KEYWORD,
Lexeme_QED_KEYWORD,
Lexeme_THEN_KEYWORD,
Lexeme_THEOREM_KEYWORD,
Lexeme_VARIABLE_KEYWORD,
Lexeme_VARIABLES_KEYWORD,
Lexeme_PROOF_STEP_ID,
Lexeme_IDENTIFIER,
Lexeme_WEAK_FAIRNESS,
Lexeme_STRONG_FAIRNESS,
Lexeme_OTHER,
Lexeme_END_OF_FILE
};
// Possible states for the lexer to enter.
enum LexState {
LexState_CONSUME_LEADING_SPACE,
LexState_FORWARD_SLASH,
LexState_BACKWARD_SLASH,
LexState_LT,
LexState_GT,
LexState_EQ,
LexState_DASH,
LexState_COMMA,
LexState_COLON,
LexState_EXCL,
LexState_DOUBLE_EXCL,
LexState_SEMICOLON,
LexState_LAND,
LexState_LOR,
LexState_L_PAREN,
LexState_R_PAREN,
LexState_R_SQUARE_BRACKET,
LexState_R_CURLY_BRACE,
LexState_R_ANGLE_BRACKET,
LexState_S, LexState_SF_,
LexState_RIGHT_ARROW,
LexState_RIGHT_MAP_ARROW,
LexState_COMMENT_START,
LexState_BLOCK_COMMENT_START,
LexState_SINGLE_LINE,
LexState_DOUBLE_LINE,
LexState_PIPE,
LexState_RIGHT_TURNSTILE,
LexState_A, LexState_ASSUM, LexState_ASSUME, LexState_ASSUMPTION, LexState_AX, LexState_AXIOM,
LexState_B, LexState_BY,
LexState_C, LexState_CO, LexState_CON, LexState_COR, LexState_CONSTANT, LexState_CONSTANTS, LexState_COROLLARY,
LexState_E, LexState_ELSE,
LexState_I, LexState_IN,
LexState_L, LexState_LE, LexState_LEMMA, LexState_LO, LexState_LOCAL,
LexState_O, LexState_OB, LexState_OBVIOUS, LexState_OM, LexState_OMITTED,
LexState_P, LexState_PRO, LexState_PROO, LexState_PROOF, LexState_PROP, LexState_PROPOSITION,
LexState_Q, LexState_QED,
LexState_T, LexState_THE, LexState_THEN, LexState_THEOREM,
LexState_V, LexState_VARIABLE, LexState_VARIABLES,
LexState_W, LexState_WF_,
LexState_IDENTIFIER,
LexState_PROOF_LEVEL_NUMBER,
LexState_PROOF_LEVEL_STAR,
LexState_PROOF_LEVEL_PLUS,
LexState_PROOF_NAME,
LexState_PROOF_ID,
LexState_OTHER,
LexState_END_OF_FILE
};
/**
* Looks ahead to identify the next lexeme. Consumes all leading
* whitespace. Out parameters include column of first non-whitespace
* codepoint and the level of the proof step ID lexeme if encountered.
*
* @param lexer The tree-sitter lexing control structure.
* @param lexeme_start_col The starting column of the first lexeme.
* @param proof_step_id_level The level of the proof step ID.
* @return The lexeme encountered.
*/
static enum Lexeme lex_lookahead(
TSLexer* const lexer,
column_index* lexeme_start_col,
CharArray* proof_step_id_level
) {
enum LexState state = LexState_CONSUME_LEADING_SPACE;
enum Lexeme result_lexeme = Lexeme_OTHER;
START_LEXER();
eof = !has_next(lexer);
switch (state) {
case LexState_CONSUME_LEADING_SPACE:
if (iswspace(lookahead)) SKIP(LexState_CONSUME_LEADING_SPACE);
*lexeme_start_col = lexer->get_column(lexer);
lexer->mark_end(lexer);
if (eof) ADVANCE(LexState_END_OF_FILE);
if ('/' == lookahead) ADVANCE(LexState_FORWARD_SLASH);
if ('\\' == lookahead) ADVANCE(LexState_BACKWARD_SLASH);
if ('<' == lookahead) ADVANCE(LexState_LT);
if ('>' == lookahead) ADVANCE(LexState_GT);
if ('=' == lookahead) ADVANCE(LexState_EQ);
if ('-' == lookahead) ADVANCE(LexState_DASH);
if (',' == lookahead) ADVANCE(LexState_COMMA);
if (':' == lookahead) ADVANCE(LexState_COLON);
if (';' == lookahead) ADVANCE(LexState_SEMICOLON);
if ('(' == lookahead) ADVANCE(LexState_L_PAREN);
if (')' == lookahead) ADVANCE(LexState_R_PAREN);
if (']' == lookahead) ADVANCE(LexState_R_SQUARE_BRACKET);
if ('}' == lookahead) ADVANCE(LexState_R_CURLY_BRACE);
if ('|' == lookahead) ADVANCE(LexState_PIPE);
if ('!' == lookahead) ADVANCE(LexState_EXCL);
if ('A' == lookahead) ADVANCE(LexState_A);
if ('B' == lookahead) ADVANCE(LexState_B);
if ('C' == lookahead) ADVANCE(LexState_C);
if ('E' == lookahead) ADVANCE(LexState_E);
if ('I' == lookahead) ADVANCE(LexState_I);
if ('L' == lookahead) ADVANCE(LexState_L);
if ('O' == lookahead) ADVANCE(LexState_O);
if ('P' == lookahead) ADVANCE(LexState_P);
if ('Q' == lookahead) ADVANCE(LexState_Q);
if ('S' == lookahead) ADVANCE(LexState_S);
if ('T' == lookahead) ADVANCE(LexState_T);
if ('V' == lookahead) ADVANCE(LexState_V);
if ('W' == lookahead) ADVANCE(LexState_W);
if (L'\u2227' == lookahead) ADVANCE(LexState_LAND); // '∧'
if (L'\u2228' == lookahead) ADVANCE(LexState_LOR); // ''
if (L'\u3009' == lookahead) ADVANCE(LexState_R_ANGLE_BRACKET); // '〉'
if (L'\u27E9' == lookahead) ADVANCE(LexState_R_ANGLE_BRACKET); // '⟩'
if (L'\u27F6' == lookahead) ADVANCE(LexState_RIGHT_ARROW); // '⟶'
if (L'\u2192' == lookahead) ADVANCE(LexState_RIGHT_ARROW); // '→'
if (L'\u27FC' == lookahead) ADVANCE(LexState_RIGHT_MAP_ARROW); // '⟼'
if (L'\u21A6' == lookahead) ADVANCE(LexState_RIGHT_MAP_ARROW); // '↦'
ADVANCE(LexState_OTHER);
END_LEX_STATE();
case LexState_FORWARD_SLASH:
ACCEPT_LEXEME(Lexeme_FORWARD_SLASH);
if ('\\' == lookahead) ADVANCE(LexState_LAND);
END_LEX_STATE();
case LexState_BACKWARD_SLASH:
ACCEPT_LEXEME(Lexeme_BACKWARD_SLASH);
if ('/' == lookahead) ADVANCE(LexState_LOR);
if ('*' == lookahead) ADVANCE(LexState_COMMENT_START);
END_LEX_STATE();
case LexState_LT:
array_push(proof_step_id_level, (char)(lookahead & CHAR_MAX));
if (iswdigit(lookahead)) ADVANCE(LexState_PROOF_LEVEL_NUMBER);
if ('*' == lookahead) ADVANCE(LexState_PROOF_LEVEL_STAR);
if ('+' == lookahead) ADVANCE(LexState_PROOF_LEVEL_PLUS);
ADVANCE(LexState_OTHER);
END_LEX_STATE();
case LexState_GT:
ACCEPT_LEXEME(Lexeme_GT);
if ('>' == lookahead) ADVANCE(LexState_R_ANGLE_BRACKET);
END_LEX_STATE();
case LexState_EQ:
ACCEPT_LEXEME(Lexeme_EQ);
if (is_next_codepoint_sequence(lexer, "===")) ADVANCE(LexState_DOUBLE_LINE);
END_LEX_STATE();
case LexState_DASH:
ACCEPT_LEXEME(Lexeme_DASH);
if ('>' == lookahead) ADVANCE(LexState_RIGHT_ARROW);
if (is_next_codepoint_sequence(lexer, "---")) ADVANCE(LexState_SINGLE_LINE);
END_LEX_STATE();
case LexState_COMMA:
ACCEPT_LEXEME(Lexeme_COMMA);
END_LEX_STATE();
case LexState_COLON:
ACCEPT_LEXEME(Lexeme_COLON);
if (':' == lookahead) ADVANCE(LexState_OTHER);
if ('=' == lookahead) ADVANCE(LexState_OTHER);
if ('>' == lookahead) ADVANCE(LexState_OTHER);
END_LEX_STATE();
case LexState_SEMICOLON:
ACCEPT_LEXEME(Lexeme_SEMICOLON);
END_LEX_STATE();
case LexState_LAND:
ACCEPT_LEXEME(Lexeme_LAND);
END_LEX_STATE();
case LexState_LOR:
ACCEPT_LEXEME(Lexeme_LOR);
END_LEX_STATE();
case LexState_L_PAREN:
ACCEPT_LEXEME(Lexeme_L_PAREN);
if ('*' == lookahead) ADVANCE(LexState_BLOCK_COMMENT_START);
END_LEX_STATE();
case LexState_R_PAREN:
ACCEPT_LEXEME(Lexeme_R_PAREN);
END_LEX_STATE();
case LexState_R_SQUARE_BRACKET:
ACCEPT_LEXEME(Lexeme_R_SQUARE_BRACKET);
END_LEX_STATE();
case LexState_R_CURLY_BRACE:
ACCEPT_LEXEME(Lexeme_R_CURLY_BRACE);
END_LEX_STATE();
case LexState_R_ANGLE_BRACKET:
ACCEPT_LEXEME(Lexeme_R_ANGLE_BRACKET);
END_LEX_STATE();
case LexState_RIGHT_ARROW:
ACCEPT_LEXEME(Lexeme_RIGHT_ARROW);
END_LEX_STATE();
case LexState_RIGHT_MAP_ARROW:
ACCEPT_LEXEME(Lexeme_RIGHT_MAP_ARROW);
END_LEX_STATE();
case LexState_COMMENT_START:
ACCEPT_LEXEME(Lexeme_COMMENT_START);
END_LEX_STATE();
case LexState_BLOCK_COMMENT_START:
ACCEPT_LEXEME(Lexeme_BLOCK_COMMENT_START);
END_LEX_STATE();
case LexState_SINGLE_LINE:
ACCEPT_LEXEME(Lexeme_SINGLE_LINE);
END_LEX_STATE();
case LexState_DOUBLE_LINE:
ACCEPT_LEXEME(Lexeme_DOUBLE_LINE);
END_LEX_STATE();
case LexState_PIPE:
if ('-' == lookahead) ADVANCE(LexState_RIGHT_TURNSTILE);
END_LEX_STATE();
case LexState_EXCL:
if ('!' == lookahead) ADVANCE(LexState_DOUBLE_EXCL);
END_LEX_STATE();
case LexState_DOUBLE_EXCL:
ACCEPT_LEXEME(Lexeme_DOUBLE_EXCL);
END_LEX_STATE();
case LexState_RIGHT_TURNSTILE:
if ('>' == lookahead) ADVANCE(LexState_RIGHT_MAP_ARROW);
END_LEX_STATE();
case LexState_A:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('X' == lookahead) ADVANCE(LexState_AX);
if (is_next_codepoint_sequence(lexer, "SSUM")) ADVANCE(LexState_ASSUM);
END_LEX_STATE();
case LexState_ASSUM:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('E' == lookahead) ADVANCE(LexState_ASSUME);
if (is_next_codepoint_sequence(lexer, "PTION")) ADVANCE(LexState_ASSUMPTION);
END_LEX_STATE();
case LexState_ASSUME:
ACCEPT_LEXEME(Lexeme_ASSUME_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_ASSUMPTION:
ACCEPT_LEXEME(Lexeme_ASSUMPTION_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_AX:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "IOM")) ADVANCE(LexState_AXIOM);
END_LEX_STATE();
case LexState_AXIOM:
ACCEPT_LEXEME(Lexeme_AXIOM_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_B:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('Y' == lookahead) ADVANCE(LexState_BY);
END_LEX_STATE();
case LexState_BY:
ACCEPT_LEXEME(Lexeme_BY_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_C:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('O' == lookahead) ADVANCE(LexState_CO);
END_LEX_STATE();
case LexState_CO:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('N' == lookahead) ADVANCE(LexState_CON);
if ('R' == lookahead) ADVANCE(LexState_COR);
END_LEX_STATE();
case LexState_CON:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "STANT")) ADVANCE(LexState_CONSTANT);
END_LEX_STATE();
case LexState_CONSTANT:
ACCEPT_LEXEME(Lexeme_CONSTANT_KEYWORD);
if ('S' == lookahead) ADVANCE(LexState_CONSTANTS);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_CONSTANTS:
ACCEPT_LEXEME(Lexeme_CONSTANTS_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_COR:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "OLLARY")) ADVANCE(LexState_COROLLARY);
END_LEX_STATE();
case LexState_COROLLARY:
ACCEPT_LEXEME(Lexeme_COROLLARY_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_E:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "LSE")) ADVANCE(LexState_ELSE);
END_LEX_STATE();
case LexState_ELSE:
ACCEPT_LEXEME(Lexeme_ELSE_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_I:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('N' == lookahead) ADVANCE(LexState_IN);
END_LEX_STATE();
case LexState_IN:
ACCEPT_LEXEME(Lexeme_IN_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_L:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('E' == lookahead) ADVANCE(LexState_LE);
if ('O' == lookahead) ADVANCE(LexState_LO);
END_LEX_STATE();
case LexState_LE:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "MMA")) ADVANCE(LexState_LEMMA);
END_LEX_STATE();
case LexState_LEMMA:
ACCEPT_LEXEME(Lexeme_LEMMA_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_LO:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "CAL")) ADVANCE(LexState_LOCAL);
END_LEX_STATE();
case LexState_LOCAL:
ACCEPT_LEXEME(Lexeme_LOCAL_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_O:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('B' == lookahead) ADVANCE(LexState_OB);
if ('M' == lookahead) ADVANCE(LexState_OM);
END_LEX_STATE();
case LexState_OB:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "VIOUS")) ADVANCE(LexState_OBVIOUS);
END_LEX_STATE();
case LexState_OBVIOUS:
ACCEPT_LEXEME(Lexeme_OBVIOUS_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_OM:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "ITTED")) ADVANCE(LexState_OMITTED);
END_LEX_STATE();
case LexState_OMITTED:
ACCEPT_LEXEME(Lexeme_OMITTED_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_P:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "RO")) ADVANCE(LexState_PRO);
END_LEX_STATE();
case LexState_PRO:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('O' == lookahead) ADVANCE(LexState_PROO);
if ('P' == lookahead) ADVANCE(LexState_PROP);
END_LEX_STATE();
case LexState_PROO:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('F' == lookahead) ADVANCE(LexState_PROOF);
END_LEX_STATE();
case LexState_PROOF:
ACCEPT_LEXEME(Lexeme_PROOF_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_PROP:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "OSITION")) ADVANCE(LexState_PROPOSITION);
END_LEX_STATE();
case LexState_PROPOSITION:
ACCEPT_LEXEME(Lexeme_PROPOSITION_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_Q:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "ED")) ADVANCE(LexState_QED);
END_LEX_STATE();
case LexState_QED:
ACCEPT_LEXEME(Lexeme_QED_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_S:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "F_")) ADVANCE(LexState_SF_);
END_LEX_STATE();
case LexState_SF_:
ACCEPT_LEXEME(Lexeme_STRONG_FAIRNESS);
END_LEX_STATE();
case LexState_T:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "HE")) ADVANCE(LexState_THE);
END_LEX_STATE();
case LexState_THE:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if ('N' == lookahead) ADVANCE(LexState_THEN);
if (is_next_codepoint_sequence(lexer, "OREM")) ADVANCE(LexState_THEOREM);
END_LEX_STATE();
case LexState_THEN:
ACCEPT_LEXEME(Lexeme_THEN_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_THEOREM:
ACCEPT_LEXEME(Lexeme_THEOREM_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_V:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "ARIABLE")) ADVANCE(LexState_VARIABLE);
END_LEX_STATE();
case LexState_VARIABLE:
ACCEPT_LEXEME(Lexeme_VARIABLE_KEYWORD);
if ('S' == lookahead) ADVANCE(LexState_VARIABLES);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_VARIABLES:
ACCEPT_LEXEME(Lexeme_VARIABLES_KEYWORD);
if (is_identifier_char(lookahead)) ADVANCE(LexState_IDENTIFIER);
END_LEX_STATE();
case LexState_W:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
if (is_next_codepoint_sequence(lexer, "F_")) ADVANCE(LexState_WF_);
END_LEX_STATE();
case LexState_WF_:
ACCEPT_LEXEME(Lexeme_WEAK_FAIRNESS);
END_LEX_STATE();
case LexState_PROOF_LEVEL_NUMBER:
if (iswdigit(lookahead)) {
array_push(proof_step_id_level, (char)(lookahead & CHAR_MAX));
ADVANCE(LexState_PROOF_LEVEL_NUMBER);
}
if ('>' == lookahead) ADVANCE(LexState_PROOF_NAME);
ADVANCE(LexState_OTHER);
END_LEX_STATE();
case LexState_PROOF_LEVEL_STAR:
if ('>' == lookahead) ADVANCE(LexState_PROOF_NAME);
ADVANCE(LexState_OTHER);
END_LEX_STATE();
case LexState_PROOF_LEVEL_PLUS:
if ('>' == lookahead) ADVANCE(LexState_PROOF_NAME);
ADVANCE(LexState_OTHER);
END_LEX_STATE();
case LexState_PROOF_NAME:
ACCEPT_LEXEME(Lexeme_PROOF_STEP_ID);
if (iswalnum(lookahead)) ADVANCE(LexState_PROOF_NAME);
if ('.' == lookahead) ADVANCE(LexState_PROOF_ID);
END_LEX_STATE();
case LexState_PROOF_ID:
ACCEPT_LEXEME(Lexeme_PROOF_STEP_ID);
if ('.' == lookahead) ADVANCE(LexState_PROOF_ID);
END_LEX_STATE();
case LexState_IDENTIFIER:
ACCEPT_LEXEME(Lexeme_IDENTIFIER);
END_LEX_STATE();
case LexState_OTHER:
ACCEPT_LEXEME(Lexeme_OTHER);
END_LEX_STATE();
case LexState_END_OF_FILE:
ACCEPT_LEXEME(Lexeme_END_OF_FILE);
END_LEX_STATE();
default:
ACCEPT_LEXEME(Lexeme_OTHER);
END_LEX_STATE();
}
}
// Tokens recognized by this scanner.
enum Token {
Token_LAND,
Token_LOR,
Token_DOUBLE_EXCL,
Token_RIGHT_DELIMITER,
Token_COMMENT_START,
Token_TERMINATOR,
Token_PROOF_STEP_ID,
Token_PROOF_KEYWORD,
Token_BY_KEYWORD,
Token_OBVIOUS_KEYWORD,
Token_OMITTED_KEYWORD,
Token_QED_KEYWORD,
Token_WEAK_FAIRNESS,
Token_STRONG_FAIRNESS,
Token_OTHER
};
/**
* Maps the given lexeme to a token.
*
* @param lexeme The lexeme to map to a token.
* @return The token corresponding to the given lexeme.
*/
static enum Token tokenize_lexeme(enum Lexeme lexeme) {
switch (lexeme) {
case Lexeme_FORWARD_SLASH: return Token_OTHER;
case Lexeme_BACKWARD_SLASH: return Token_OTHER;
case Lexeme_GT: return Token_OTHER;
case Lexeme_EQ: return Token_OTHER;
case Lexeme_DASH: return Token_OTHER;
case Lexeme_COMMA: return Token_RIGHT_DELIMITER;
case Lexeme_COLON: return Token_RIGHT_DELIMITER;
case Lexeme_SEMICOLON: return Token_TERMINATOR;
case Lexeme_LAND: return Token_LAND;
case Lexeme_LOR: return Token_LOR;
case Lexeme_DOUBLE_EXCL: return Token_DOUBLE_EXCL;
case Lexeme_L_PAREN: return Token_OTHER;
case Lexeme_R_PAREN: return Token_RIGHT_DELIMITER;
case Lexeme_R_SQUARE_BRACKET: return Token_RIGHT_DELIMITER;
case Lexeme_R_CURLY_BRACE: return Token_RIGHT_DELIMITER;
case Lexeme_R_ANGLE_BRACKET: return Token_RIGHT_DELIMITER;
case Lexeme_RIGHT_ARROW: return Token_RIGHT_DELIMITER;
case Lexeme_RIGHT_MAP_ARROW: return Token_RIGHT_DELIMITER;
case Lexeme_COMMENT_START: return Token_COMMENT_START;
case Lexeme_BLOCK_COMMENT_START: return Token_COMMENT_START;
case Lexeme_SINGLE_LINE: return Token_TERMINATOR;
case Lexeme_DOUBLE_LINE: return Token_TERMINATOR;
case Lexeme_ASSUME_KEYWORD: return Token_TERMINATOR;
case Lexeme_ASSUMPTION_KEYWORD: return Token_TERMINATOR;
case Lexeme_AXIOM_KEYWORD: return Token_TERMINATOR;
case Lexeme_BY_KEYWORD: return Token_BY_KEYWORD;
case Lexeme_CONSTANT_KEYWORD: return Token_TERMINATOR;
case Lexeme_CONSTANTS_KEYWORD: return Token_TERMINATOR;
case Lexeme_COROLLARY_KEYWORD: return Token_TERMINATOR;
case Lexeme_ELSE_KEYWORD: return Token_RIGHT_DELIMITER;
case Lexeme_IN_KEYWORD: return Token_RIGHT_DELIMITER;
case Lexeme_LEMMA_KEYWORD: return Token_TERMINATOR;
case Lexeme_LOCAL_KEYWORD: return Token_TERMINATOR;
case Lexeme_OBVIOUS_KEYWORD: return Token_OBVIOUS_KEYWORD;
case Lexeme_OMITTED_KEYWORD: return Token_OMITTED_KEYWORD;
case Lexeme_PROOF_KEYWORD: return Token_PROOF_KEYWORD;
case Lexeme_PROPOSITION_KEYWORD: return Token_TERMINATOR;
case Lexeme_THEN_KEYWORD: return Token_RIGHT_DELIMITER;
case Lexeme_THEOREM_KEYWORD: return Token_TERMINATOR;
case Lexeme_VARIABLE_KEYWORD: return Token_TERMINATOR;
case Lexeme_VARIABLES_KEYWORD: return Token_TERMINATOR;
case Lexeme_PROOF_STEP_ID: return Token_PROOF_STEP_ID;
case Lexeme_QED_KEYWORD: return Token_QED_KEYWORD;
case Lexeme_STRONG_FAIRNESS: return Token_STRONG_FAIRNESS;
case Lexeme_WEAK_FAIRNESS: return Token_WEAK_FAIRNESS;
case Lexeme_IDENTIFIER: return Token_OTHER;
case Lexeme_OTHER: return Token_OTHER;
case Lexeme_END_OF_FILE: return Token_TERMINATOR;
default: return Token_OTHER;
}
}
// Possible types of junction list.
enum JunctType {
JunctType_CONJUNCTION,
JunctType_DISJUNCTION
};
// Data about a jlist.
struct JunctList {
// The type of jlist.
enum JunctType type;
// The starting alignment columnt of the jlist.
column_index alignment_column;
};
static struct JunctList create_junctlist(enum JunctType const type, column_index const alignment_column) {
struct JunctList jlist;
jlist.type = type;
jlist.alignment_column = alignment_column;
return jlist;
}
static unsigned jlist_serialize(struct JunctList* this, char* const buffer, bool const is_dry_run) {
unsigned offset = 0;
unsigned copied = 0;
// Serialize junction type
copied = sizeof(uint8_t);
if (!is_dry_run) { buffer[offset] = (uint8_t)(this->type); }
offset += copied;
// Serialize alignment column
copied = sizeof(column_index);
if (!is_dry_run) { memcpy(&buffer[offset], &this->alignment_column, copied); }
offset += copied;
return offset;
}
static unsigned jlist_deserialize(struct JunctList* this, const char* const buffer) {
unsigned offset = 0;
unsigned copied = 0;
// Deserialize junction type
copied = sizeof(uint8_t);
this->type = (enum JunctType)buffer[offset];
offset += copied;
// Deserialize alignment column
copied = sizeof(column_index);
memcpy(&this->alignment_column, &buffer[offset], copied);
offset += copied;
return offset;
}
/**
* A stateful scanner used to parse junction lists and proofs.
*/
struct Scanner {
// The nested junction lists at the current lexer position.
Array(struct JunctList) jlists;
// The nested proofs at the current lexer position.
Array(proof_level) proofs;
// The level of the last proof.
proof_level last_proof_level;
// Whether we have seen a PROOF token.
bool have_seen_proof_keyword;
};
/**
* Serializes the Scanner state into the given buffer.
*
* @param this The Scanner state to serialize.
* @param buffer The buffer into which to serialize the scanner state.
* @param is_dry_run Whether to actually copy the bytes to the buffer.
* @return Number of bytes written into the buffer.
*/
static unsigned scanner_try_serialize(
const struct Scanner* const this,
char* const buffer,
const bool is_dry_run
) {
unsigned offset = 0;
unsigned copied = 0;
const nest_address jlist_depth = (nest_address)(this->jlists.size);
copied = sizeof(nest_address);
if (!is_dry_run) { memcpy(&buffer[offset], &jlist_depth, copied); }
offset += copied;
for (nest_address i = 0; i < jlist_depth; i++) {
char* const buffer_addr = is_dry_run ? NULL : &buffer[offset];
copied = jlist_serialize(array_get(&this->jlists, i), buffer_addr, is_dry_run);
offset += copied;
}
const nest_address proof_depth = (nest_address)(this->proofs.size);
copied = sizeof(nest_address);
if (!is_dry_run) { memcpy(&buffer[offset], &proof_depth, copied); }
offset += copied;
copied = proof_depth * sizeof(proof_level);
if (!is_dry_run && copied > 0) { memcpy(&buffer[offset], this->proofs.contents, copied); }
offset += copied;
copied = sizeof(proof_level);
if (!is_dry_run) { memcpy(&buffer[offset], &this->last_proof_level, copied); }
offset += copied;
copied = sizeof(uint8_t);
if (!is_dry_run) { buffer[offset] = (uint8_t)(this->have_seen_proof_keyword); }
offset += copied;
return offset;
}
/**
* Serializes the Scanner state into the given buffer.
*
* @param this The Scanner state to serialize.
* @param buffer The buffer into which to serialize the scanner state.
* @return Number of bytes written into the buffer.
*/
static unsigned scanner_serialize(const struct Scanner* this, char* buffer) {
return scanner_try_serialize(this, buffer, false);
}
/**
* Calculates the serialized size of the scanner.
*
* @param this The Scanner state to find the deserialized size of.
* @return The size of the Scanner when serialized.
*/
static unsigned scanner_serialized_size(const struct Scanner* const this) {
return scanner_try_serialize(this, NULL, true);
}
/**
* Deserializes the Scanner state from the given buffer.
*
* @param this The Scanner state to deserialize.
* @param buffer The buffer from which to deserialize the state.
* @param length The bytes available to read from the buffer.
*/
static void scanner_deserialize(struct Scanner* const this, const char* const buffer, unsigned const length) {
// Very important to clear values of all fields here!
// Scanner object is reused; if a variable isn't cleared, it can
// lead to extremely strange & impossible-to-debug behavior.
array_delete(&this->jlists);
array_delete(&this->proofs);
this->last_proof_level = -1;
this->have_seen_proof_keyword = false;
if (length > 0) {
unsigned offset = 0;
unsigned copied = 0;
nest_address jlist_depth = 0;
copied = sizeof(nest_address);
memcpy(&jlist_depth, &buffer[offset], copied);
array_grow_by(&(this->jlists), jlist_depth);
offset += copied;
for (nest_address i = 0; i < jlist_depth; i++) {
assert(offset < length);
copied = jlist_deserialize(array_get(&(this->jlists), i), &buffer[offset]);
offset += copied;
}
nest_address proof_depth = 0;
copied = sizeof(nest_address);
memcpy(&proof_depth, &buffer[offset], copied);
array_grow_by(&(this->proofs), proof_depth);
offset += copied;
copied = proof_depth * sizeof(proof_level);
if (copied > 0) { memcpy(this->proofs.contents, &buffer[offset], copied); }
offset += copied;
copied = sizeof(proof_level);
memcpy(&(this->last_proof_level), &buffer[offset], copied);
offset += copied;
copied = sizeof(uint8_t);
this->have_seen_proof_keyword = (bool)(buffer[offset] & 1);
offset += copied;
assert(offset == length);
}
}
/**
* Initializes a new instance of the Scanner object.
*
* @return A newly-created Scanner.
*/
static struct Scanner scanner_create() {
struct Scanner s;
array_init(&s.jlists);
array_init(&s.proofs);
s.last_proof_level = -1;
s.have_seen_proof_keyword = false;
return s;
}
/**
* Frees all memory associated with this Scanner.
*
* @param this The Scanner to free.
*/
static void scanner_free(struct Scanner* const this) {
array_delete(&this->jlists);
array_delete(&this->proofs);
}
/**
* Whether the Scanner state indicates we are currently in a jlist.
*
* @param this The Scanner state.
* @return Whether we are in a jlist.
*/
static bool is_in_jlist(const struct Scanner* const this) {
return this->jlists.size > 0;
}
/**
* The column index of the current jlist. Returns negative number if
* we are not currently in a jlist.
*
* @param this The Scanner state.
* @return The column index of the current jlist.
*/
static column_index get_current_jlist_column_index(const struct Scanner* const this) {
return is_in_jlist(this) ? array_back(&this->jlists)->alignment_column : -1;
}
/**
* Whether the given jlist type matches the current jlist.
*
* @param this The Scanner state.
* @param type The jlist type to check.
* @return Whether the given jlist type matches the current jlist.
*/
static bool current_jlist_type_is(
struct Scanner* const this,
enum JunctType const type
) {
return is_in_jlist(this) && type == array_back(&this->jlists)->type;
}
/**
* Assuming the lexer is situated directly after a junct token, read
* to the next non-whitespace char to check whether it is a , or ) token.
* If so, this is not the start of a new jlist; instead, it is a higher-
* level parameter to an operator, like op(x, /\) or op(\/, x).
*
* @param lexer The tree-sitter lexing control structure.
*/
static bool is_junct_token_higher_level_op_parameter(TSLexer* const lexer) {
while (iswspace(lexer->lookahead) && has_next(lexer)) {
lexer->advance(lexer, true);
}
return ',' == lexer->lookahead || ')' == lexer->lookahead;
}
/**
* Emits an INDENT token, recording the new jlist in the Scanner state.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param type The type of the new jlist.
* @param col The column position of the new jlist.
* @return Whether an INDENT token was emitted.
*/
static bool emit_indent(
struct Scanner* const this,
TSLexer* const lexer,
enum JunctType const type,
column_index const col
) {
lexer->result_symbol = INDENT;
struct JunctList new_list = create_junctlist(type, col);
array_push(&this->jlists, new_list);
return true;
}
/**
* Emits a BULLET token, marking the start of a new item in the current
* jlist.
*
* @param lexer The tree-sitter lexing control structure.
* @return Whether a BULLET token was emitted.
*/
static bool emit_bullet(TSLexer* const lexer) {
lexer->result_symbol = BULLET;
return true;
}
/**
* Emits a DEDENT token, removing a jlist from the Scanner state.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @return Whether a DEDENT token was emitted.
*/
static bool emit_dedent(struct Scanner* const this, TSLexer* const lexer) {
if (is_in_jlist(this)) {
lexer->result_symbol = DEDENT;
(void)array_pop(&this->jlists);
return true;
} else {
return false;
}
}
/**
* Jlists are identified with the column position (cpos) of the first
* junct token in the list, and the junction type. For a given junct
* token there are five possible interpretations:
* 1. The junct is after the cpos of the current jlist, and an
* INDENT token is expected
* -> this is a new nested jlist, emit INDENT token
* 2. The junct is after the cpos of the current jlist, and an
* INDENT token is *not* expected
* -> this is an infix junct operator; emit nothing
* 3. The junct is equal to the cpos of the current jlist, and is
* the same junct type (conjunction or disjunction)
* -> this is an item of the current jlist; emit BULLET token
* 4. The junct is equal to the cpos of the current jlist, and is
* a DIFFERENT junct type (conjunction vs. disjunction)
* -> this is an infix operator that also ends the current list
* 5. The junct is prior to the cpos of the current jlist
* -> this ends the current jlist, emit DEDENT token
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param valid_symbols Tokens possibly expected in this spot.
* @param type The type of junction encountered.
* @param next The column position of the junct token encountered.
* @return Whether a jlist-relevant token should be emitted.
*/
static bool handle_junct_token(
struct Scanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols,
enum JunctType const next_type,
column_index const next_col
) {
const column_index current_col = get_current_jlist_column_index(this);
if (current_col < next_col) {
if (valid_symbols[INDENT]) {
if (is_junct_token_higher_level_op_parameter(lexer)) {
/**
* Handle case op(x, /\) or op(\/, x) - junct token is the first
* token of a new expression and thus could be a new jlist, but
* actually is a higher-level operator parameter.
*/
return false;
} else {
/**
* The start of a new junction list!
*/
return emit_indent(this, lexer, next_type, next_col);
}
} else {
/**
* This is an infix junction symbol. Tree-sitter will only look for
* a new jlist at the start of an expression rule; infix operators
* occur when joining two expression rules together, so tree-sitter
* is only looking for either BULLET or DEDENT rules. Examples:
*
* /\ a /\ b
* ^ tree-sitter will NEVER look for an INDENT here
*
* /\ a
* /\ b
* ^ tree-sitter WILL look for a BULLET here
*
* /\ /\ a
* ^ tree-sitter WILL look for an INDENT here
*/
return false;
}
} else if (current_col == next_col) {
if (current_jlist_type_is(this, next_type)) {
/**
* This is another entry in the jlist.
*/
return emit_bullet(lexer);
} else {
/**
* Disjunct in alignment with conjunct list or vice-versa; treat
* this as an infix operator by terminating the current list.
*/
return emit_dedent(this, lexer);
}
} else {
/**
* Junct found prior to the alignment column of the current jlist.
* This marks the end of the jlist.
*/
return emit_dedent(this, lexer);
}
}
/**
* If a given right delimiter matches some left delimiter that occurred
* *before* the beginning of the current jlist, then that ends the
* current jlist. The concept of a delimiter is not limited (hah) to
* (), [], <<>>, and {}; it also includes IF/THEN, THEN/ELSE, CASE/->,
* and basically every other language construct where an expression is
* squeezed between a known start & end token.
*
* Previously I implemented complicated logic using a stack to keep
* track of all the delimiters that have been seen (and their
* pairs) but found that tree-sitter would never trigger the
* external scanner before encountering a right delimiter matching
* a left delimiter that started within the scope of a jlist. Thus
* we can assume that when we *do* see a right delimiter, it
* matches a left delimiter that occurred prior to the start of the
* jlist, so we can emit a DEDENT token to end the jlist. Example:
*
* /\ ( a + b )
* ^ tree-sitter will never look for an INDENT,
* BULLET, or DEDENT token here; it is only
* looking for another infix operator or the
* right-delimiter.
*
* ( /\ a + b )
* ^ tree-sitter WILL look for an INDENT, BULLET, or
* DEDENT token here in addition to looking for an
* infix operator; it also wants to see a DEDENT
* token before seeing the right delimiter, although
* error recovery is simple enough that it would
* barely notice its absence.
*
* There are a few notable exceptions to this rule; for example, the
* empty set or empty sequence:
*
* /\ { }
* ^
* /\ << >>
* ^ there is the option for an expression here, so tree-sitter
* looks for INDENT tokens and we will see a right delimiter
* in this external scanner.
*
* Another example when the code is in a non-parseable state which we
* nonetheless wish to handle gracefully:
*
* /\ [x \in S |-> ]
* ^ user is about to write an expression here, but
* there is a time when the code is non-parseable;
* tree-sitter will again look for an INDENT token
* and we will see a right delimiter in this
* external scanner.
*
* The easy solution to these cases is to simply check whether
* tree-sitter is looking for a DEDENT token. If so, emit one; if not,
* emit nothing. Tree-sitter will not look for a DEDENT token inside
* enclosing delimiters within the scope of a jlist.
*
* One side-effect of all this is that tree-sitter parses certain
* arrangements of jlists and delimiters that are actually illegal
* according to TLA⁺ syntax rules; that is okay since tree-sitter's
* use case of error-tolerant editor tooling ensures its design
* errs on the side of being overly-permissive. For a concrete
* example here, tree-sitter will parse this illegal expression
* without complaint:
*
* /\ A
* /\ (B + C
* )
* /\ D
*
* This should simply be detected as an error at the semantic level.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param valid_symbols Tokens possibly expected in this spot.
* @return Whether a jlist-relevant token should be emitted.
*/
static bool handle_right_delimiter_token(
struct Scanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols
) {
return is_in_jlist(this)
&& valid_symbols[DEDENT]
&& emit_dedent(this, lexer);
}
/**
* Emits a dedent token if are in jlist and have encountered a token that
* unconditionally ends a jlist regardless of column position; these
* include:
* 1. New unit definition (op == expr, etc.)
* 2. End-of-module token (====)
* 3. End-of-file (this shouldn't happen but we will end the jlist to
* improve error reporting since the end-of-module token is missing)
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @return Whether a jlist-relevant token should be emitted.
*/
static bool handle_terminator_token(
struct Scanner* const this,
TSLexer* const lexer
) {
return is_in_jlist(this) && emit_dedent(this, lexer);
}
/**
* Non-junct tokens could possibly indicate the end of a jlist. Rules:
* - If the token cpos is leq to the current jlist cpos, the jlist
* has ended; emit a DEDENT token (possibly multiple); example:
* IF /\ P
* /\ Q
* THEN R
* ELSE S
* - Otherwise the token is treated as part of the expression in that
* junct; for example:
* /\ IF e THEN P
* ELSE Q
* /\ R
* so emit no token.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param next The column position of the encountered token.
* @return Whether a jlist-relevant token should be emitted.
*/
static bool handle_other_token(
struct Scanner* const this,
TSLexer* const lexer,
column_index const next
) {
return is_in_jlist(this)
&& next <= get_current_jlist_column_index(this)
&& emit_dedent(this, lexer);
}
/**
* Handles the infix !! operator. This has a lexical conflict with the
* subexpression separator !, so an expression like A!!!B will be parsed
* incorrectly as A !! (!B) instead of (A!) !! B. Ideally tree-sitter
* would add negative-lookahead in regexes so this wouldn't have to be
* corrected in the external scanner, but it is what it is.
*
* This function first checks whether the !! token is interfering with an
* existing jlist. If not, it checks the next character to see whether it
* is !. If so, this function emits false - no !! token found - under the
* assumption that this is a A!!!B scenario so this string should be
* split into (!)(!!) instead of (!!)(!).
*
* Note there is a case A!!!!B which should be grouped as (A!) (!!) (!B)
* but will not be according to these rules. The workaround for this case
* (as with several other lexing ambiguities in this language) is to
* insert a space for disambiguation, like A!!! !B. Humorously, this does
* mean that !!!!!!!!!!!!!!!!!!!!!! (ad infinitum) is valid TLA+ syntax.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param next The column position of the encountered token.
*/
static bool handle_double_excl_token(
struct Scanner* const this,
TSLexer* const lexer,
column_index const next
) {
if (handle_other_token(this, lexer, next)) {
// This token is affecting a jlist, emit dedent
return true;
} else {
if ('!' == lexer->lookahead) {
return false;
} else {
lexer->mark_end(lexer);
lexer->result_symbol = DOUBLE_EXCL;
return true;
}
}
}
/**
* Gets whether we are currently in a proof.
*
* @param this The Scanner state.
* @return Whether we are currently in a proof.
*/
static bool is_in_proof(const struct Scanner* const this) {
return this->proofs.size > 0;
}
/**
* Gets the current proof level; -1 if none.
*
* @param this The Scanner state.
* @return The current proof level.
*/
static proof_level get_current_proof_level(const struct Scanner* const this) {
return is_in_proof(this) ? *array_back(&this->proofs) : -1;
}
/**
* Emits a token indicating the start of a new proof.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param level The level of the new proof.
* @return Whether a token should be emitted.
*/
static bool emit_begin_proof(
struct Scanner* const this,
TSLexer* const lexer,
proof_level level
) {
lexer->result_symbol = BEGIN_PROOF;
array_push(&this->proofs, level);
this->last_proof_level = level;
this->have_seen_proof_keyword = false;
return true;
}
/**
* Emits a token indicating the start of a new proof step.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param level The level of the new proof step.
* @return Whether a token should be emitted.
*/
static bool emit_begin_proof_step(
struct Scanner* const this,
TSLexer* const lexer,
proof_level level
) {
this->last_proof_level = level;
lexer->result_symbol = BEGIN_PROOF_STEP;
return true;
}
/**
* Handle encountering a new proof step ID. This probably marks the
* beginning of a new proof step, but could also be a reference to a
* prior proof step as part of an expression. There are also various
* interactions between the proof step ID and jlists. Cases:
* 1. A proof step token is expected
* -> This is a new proof step; see proof step logic below
* 2. A proof step token is *not* expected, and a DEDENT token *is*
* -> This is the beginning of a proof step but there is an open
* jlist which must first be closed; emit a DEDENT token.
* 3. A proof step token is not expected, and neither is a DEDENT
* -> This is a proof step reference, so treat as other token.
* P => <1>b
* ^ tree-sitter will only look for INDENT here
*
* For handling proof steps alone, there are the following cases:
* 1. The new proof token level is greater than the current level
* -> This is the start of a new proof; emit BEGIN_PROOF token
* and push level to proof stack. Set last_proof_level to
* the new proof level.
* 2. The new proof token level is equal to the current level
* -> This is another proof step; emit BEGIN_PROOF_STEP token.
* 3. The new proof token level is less than the current level
* -> This is an error, which we will try to recover from.
*
* There are also rules to handle proof step IDs where the level is
* inferred, like <+> and <*>. They are as follows:
* 1. The proof step ID is <+>
* -> This is the start of a new proof; its level is one higher
* than last_proof_level.
* 2. The proof step ID is <*> and we are not inside a proof
* -> This is the start of the very first proof; its level is one
* higher than last_proof_level, which should be -1; thus the
* proof level should be 0.
* 3. The proof step ID is <*> and it directly follows a PROOF keyword
* -> This is the start of a new proof; its level is one higher
* than last_proof_level.
* 4. The proof step ID is <*> and it *does not* follow a PROOF keyword
* -> This is another step in the same proof; its level is the
* same as last_proof_level.
*
* Proofs are ended upon encountering a QED step, which is handled
* elsewhere.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param valid_symbols Tokens possibly expected in this spot.
* @param next The column position of the encountered token.
* @return Whether a token should be emitted.
*/
static bool handle_proof_step_id_token(
struct Scanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols,
column_index const next,
struct ProofStepId proof_step_id_token
) {
assert(ProofStepIdType_NONE != proof_step_id_token.type);
if (valid_symbols[BEGIN_PROOF] || valid_symbols[BEGIN_PROOF_STEP]) {
proof_level next_proof_level = -1;
const proof_level current_proof_level = get_current_proof_level(this);
switch (proof_step_id_token.type) {
case ProofStepIdType_STAR:
/**
* <*> can start a new proof only at the very first level,
* or directly following a PROOF keyword.
*/
next_proof_level =
!is_in_proof(this) || this->have_seen_proof_keyword
? this->last_proof_level + 1
: current_proof_level;
break;
case ProofStepIdType_PLUS:
/**
* This keeps us from entering an infinite loop when we see
* a <+> proof step ID; the first time we encounter the <+>,
* we will increase the level and emit a BEGIN_PROOF token.
* The second time, we mark it as the same level and emit a
* BEGIN_PROOF_STEP token.
*/
next_proof_level = valid_symbols[BEGIN_PROOF]
? this->last_proof_level + 1
: current_proof_level;
break;
case ProofStepIdType_NUMBERED:
next_proof_level = proof_step_id_token.level;
break;
default:
return false;
}
if (next_proof_level > current_proof_level) {
return emit_begin_proof(this, lexer, next_proof_level);
} else if (next_proof_level == current_proof_level) {
if (this->have_seen_proof_keyword) {
// This has been declared a new proof using the PROOF keyword
// but does not have a level greater than the old; thus we've
// detected a syntax error.
// TODO: handle this.
return false;
} else {
return emit_begin_proof_step(this, lexer, next_proof_level);
}
} else {
// The next proof level is lower than the current. This is
// invalid syntax.
// TODO: handle this.
return false;
}
} else {
if (valid_symbols[DEDENT]) {
// End all jlists before start of proof.
return handle_terminator_token(this, lexer);
} else {
// This is a reference to a proof step in an expression.
return handle_other_token(this, lexer, next);
}
}
}
/**
* Handles the PROOF keyword token. We record that we've seen the
* PROOF keyword, which modifies the interpretation of the subsequent
* proof step ID. The PROOF token also terminates any current jlist.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param valid_symbols Tokens possibly expected in this spot.
* @return Whether a token should be emitted.
*/
static bool handle_proof_keyword_token(
struct Scanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols
) {
if (valid_symbols[PROOF_KEYWORD]) {
this->have_seen_proof_keyword = true;
lexer->result_symbol = PROOF_KEYWORD;
lexer->mark_end(lexer);
return true;
} else {
return handle_terminator_token(this, lexer);
}
}
/**
* Handles the BY, OBVIOUS, and OMITTED keyword tokens. We record
* that we've seen the keyword, which negates any PROOF keyword
* previously encountered. These tokens also terminate any current
* jlist.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param valid_symbols Tokens possibly expected in this spot.
* @param keyword_type The specific keyword being handled.
* @return Whether a token should be emitted.
*/
static bool handle_terminal_proof_keyword_token(
struct Scanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols,
enum TokenType keyword_type
) {
if (valid_symbols[keyword_type]) {
this->have_seen_proof_keyword = false;
lexer->result_symbol = keyword_type;
lexer->mark_end(lexer);
return true;
} else {
return handle_terminator_token(this, lexer);
}
}
/**
* Handles the QED keyword token. The QED token indicates this is the
* final step of a proof, so we modify the state accordingly. First
* we record the current proof level in case there is a child proof
* of this step that uses <+> or PROOF <*> for its first step. Then
* we pop the top proof level off the stack.
*
* It's possible to encounter a QED keyword while not inside of a proof
* as part of an earlier syntax error, and proof step IDs being treated
* as sequences of < and > operators. In this case the current proof
* state should not be modified, but a QED keyword token is still
* returned to help the error recovery process. Not performing this
* check previously led to a segfault; see:
* https://github.com/tlaplus-community/tree-sitter-tlaplus/issues/60
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @return Whether a token should be emitted.
*/
static bool handle_qed_keyword_token(
struct Scanner* const this,
TSLexer* const lexer
) {
if (is_in_proof(this)) {
this->last_proof_level = get_current_proof_level(this);
(void)array_pop(&this->proofs);
}
lexer->result_symbol = QED_KEYWORD;
lexer->mark_end(lexer);
return true;
}
/**
* Handles the fairness tokens WF_ and SF_.
* Need to handle this in an external scanner due to:
* https://github.com/tree-sitter/tree-sitter/issues/1615
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param next The column position of the encountered token.
* @param keyword_type The specific keyword being handled.
* @return Whether a token should be emitted.
*/
static bool handle_fairness_keyword_token(
struct Scanner* const this,
TSLexer* const lexer,
column_index const next,
enum TokenType keyword_type
) {
if (handle_other_token(this, lexer, next)) {
return true;
} else {
lexer->result_symbol = keyword_type;
lexer->mark_end(lexer);
return true;
}
}
/**
* Scans for various possible external tokens.
*
* @param this The Scanner state.
* @param lexer The tree-sitter lexing control structure.
* @param valid_symbols Tokens possibly expected in this spot.
* @return Whether a token was encountered.
*/
static bool scan(
struct Scanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols
) {
// All symbols are marked as valid during error recovery.
// We can check for this by looking at the validity of the final
// (unused) external symbol, ERROR_SENTINEL.
const bool is_error_recovery = valid_symbols[ERROR_SENTINEL];
// TODO: actually function during error recovery
// https://github.com/tlaplus-community/tree-sitter-tlaplus/issues/19
if (is_error_recovery) {
return false;
}
if(valid_symbols[LEADING_EXTRAMODULAR_TEXT] || valid_symbols[TRAILING_EXTRAMODULAR_TEXT]) {
return scan_extramodular_text(lexer, valid_symbols);
} else {
column_index col = -1;
CharArray proof_step_id_level = array_new();
enum Token token = tokenize_lexeme(lex_lookahead(lexer, &col, &proof_step_id_level));
struct ProofStepId proof_step_id_token = parse_proof_step_id(&proof_step_id_level);
array_delete(&proof_step_id_level);
switch (token) {
case Token_LAND:
return handle_junct_token(this, lexer, valid_symbols, JunctType_CONJUNCTION, col);
case Token_LOR:
return handle_junct_token(this, lexer, valid_symbols, JunctType_DISJUNCTION, col);
case Token_RIGHT_DELIMITER:
return handle_right_delimiter_token(this, lexer, valid_symbols);
case Token_COMMENT_START:
return false;
case Token_TERMINATOR:
return handle_terminator_token(this, lexer);
case Token_PROOF_STEP_ID:
return handle_proof_step_id_token(this, lexer, valid_symbols, col, proof_step_id_token);
case Token_PROOF_KEYWORD:
return handle_proof_keyword_token(this, lexer, valid_symbols);
case Token_BY_KEYWORD:
return handle_terminal_proof_keyword_token(this, lexer, valid_symbols, BY_KEYWORD);
case Token_OBVIOUS_KEYWORD:
return handle_terminal_proof_keyword_token(this, lexer, valid_symbols, OBVIOUS_KEYWORD);
case Token_OMITTED_KEYWORD:
return handle_terminal_proof_keyword_token(this, lexer, valid_symbols, OMITTED_KEYWORD);
case Token_QED_KEYWORD:
return handle_qed_keyword_token(this, lexer);
case Token_WEAK_FAIRNESS:
return handle_fairness_keyword_token(this, lexer, col, WEAK_FAIRNESS);
case Token_STRONG_FAIRNESS:
return handle_fairness_keyword_token(this, lexer, col, STRONG_FAIRNESS);
case Token_DOUBLE_EXCL:
return handle_double_excl_token(this, lexer, col);
case Token_OTHER:
return handle_other_token(this, lexer, col);
default:
return false;
}
}
}
/**
* A hierarchy of nested stateful scanners.
* Each time a PlusCal block is entered, a nested context is created.
* Exiting the PlusCal block exits the context.
* Multiply-nested PlusCal blocks are supported.
*/
struct NestedScanner {
// The enclosing context(s) of the PlusCal block.
Array(CharArray) enclosing_contexts;
// The currently-active context.
struct Scanner current_context;
};
/**
* Serialize the nested scanner into a buffer.
*
* @param this The NestedScanner state.
* @param buffer The buffer to serialize the state into.
* @return The number of bytes written into the buffer.
*/
static unsigned nested_scanner_serialize(
const struct NestedScanner* const this,
char* const buffer
) {
unsigned offset = 0;
unsigned copied = 0;
// First write number of enclosing contexts (guaranteed to be >= 1)
nest_address const context_depth = this->enclosing_contexts.size + 1;
copied = sizeof(nest_address);
if (copied > 0) memcpy(&buffer[offset], &context_depth, copied);
offset += copied;
// Then write size of N-1 enclosing contexts
for (int i = 0; i < context_depth - 1; i++) {
unsigned const context_size = array_get(&this->enclosing_contexts, i)->size;
copied = sizeof(unsigned);
if (copied > 0) memcpy(&buffer[offset], &context_size, copied);
offset += copied;
}
// Reserve space for current context size
unsigned const current_context_size_offset = offset;
copied = sizeof(unsigned);
offset += copied;
// Serialize N-1 enclosing contexts
for (unsigned i = 0; i < this->enclosing_contexts.size; i++) {
CharArray* context = array_get(&this->enclosing_contexts, i);
copied = context->size;
if (copied > 0) memcpy(&buffer[offset], context->contents, copied);
offset += copied;
}
// Serialize current context
copied = scanner_serialize(&this->current_context, &buffer[offset]);
offset += copied;
// Write current context size to reserved position
memcpy(&buffer[current_context_size_offset], &copied, sizeof(unsigned));
return offset;
}
/**
* Deserialize a nested scanner.
*
* @param this The nested scanner instance to deserialize into.
* @param buffer The buffer to deserialize from.
* @param length The number of bytes in the buffer.
*/
static void nested_scanner_deserialize(
struct NestedScanner* const this,
const char* const buffer,
unsigned const length
) {
for (unsigned i = 0; i < this->enclosing_contexts.size; i++) {
array_delete(array_get(&this->enclosing_contexts, i));
}
array_delete(&this->enclosing_contexts);
scanner_deserialize(&this->current_context, NULL, 0);
if (length > 0) {
unsigned offset = 0;
unsigned copied = 0;
// First item: total number of contexts (guaranteed to be >= 1)
nest_address context_depth = 0;
copied = sizeof(nest_address);
memcpy(&context_depth, &buffer[offset], copied);
assert(1 <= context_depth);
array_grow_by(&this->enclosing_contexts, context_depth - 1);
offset += copied;
// Next N items: size of all contexts
Array(unsigned) context_sizes = array_new();
array_grow_by(&context_sizes, context_depth);
copied = context_depth * sizeof(unsigned);
if (copied > 0 && context_sizes.contents != NULL) memcpy(context_sizes.contents, &buffer[offset], copied);
offset += copied;
// Deserialize N-1 contexts as enclosing contexts
for (int i = 0; i < context_depth - 1; i++) {
copied = *array_get(&context_sizes, i);
array_grow_by(array_get(&this->enclosing_contexts, i), copied);
if (copied > 0) memcpy(array_get(&this->enclosing_contexts, i)->contents, &buffer[offset], copied);
offset += copied;
}
// Final context is deserialized as current context
copied = *array_back(&context_sizes);
scanner_deserialize(&this->current_context, &buffer[offset], copied);
offset += copied;
array_delete(&context_sizes);
assert(offset == length);
}
}
/**
* Initializes a new instance of the NestedScanner object.
*
* @param this The NestedScanner to initialize.
*/
static void nested_scanner_init(struct NestedScanner* const this) {
array_init(&this->enclosing_contexts);
this->current_context = scanner_create();
}
/**
* Frees all memory allocated by the nested scanner.
*
* @param this The NestedScanner to free.
*/
static void nested_scanner_free(struct NestedScanner* const this) {
for (unsigned i = 0; i < this->enclosing_contexts.size; i++) {
array_delete(array_get(&this->enclosing_contexts, i));
}
array_delete(&this->enclosing_contexts);
scanner_free(&this->current_context);
}
static bool nested_scan(
struct NestedScanner* const this,
TSLexer* const lexer,
const bool* const valid_symbols) {
// All symbols are marked as valid during error recovery.
// We can check for this by looking at the validity of the final
// (unused) external symbol, ERROR_SENTINEL.
if (valid_symbols[ERROR_SENTINEL]) {
return false;
} else if (valid_symbols[PCAL_START]) {
// Entering PlusCal block; push current context then clear
unsigned const expected_size = scanner_serialized_size(&this->current_context);
CharArray serialized_current_context = array_new();
array_grow_by(&serialized_current_context, expected_size);
unsigned const actual_size = scanner_serialize(&this->current_context, serialized_current_context.contents);
assert(expected_size == actual_size);
array_push(&this->enclosing_contexts, serialized_current_context);
scanner_free(&this->current_context);
this->current_context = scanner_create();
lexer->result_symbol = PCAL_START;
return true;
} else if (valid_symbols[PCAL_END] && this->enclosing_contexts.size > 0) {
// Exiting PlusCal block; rehydrate context then pop
CharArray* next = array_back(&this->enclosing_contexts);
scanner_deserialize(&this->current_context, next->contents, next->size);
CharArray ctx = array_pop(&this->enclosing_contexts);
array_delete(&ctx);
lexer->result_symbol = PCAL_END;
return true;
} else {
return scan(&this->current_context, lexer, valid_symbols);
}
}
// Called once when language is set on a parser.
// Allocates memory for storing scanner state.
void* tree_sitter_tlaplus_external_scanner_create() {
struct NestedScanner* scanner = ts_malloc(sizeof(struct NestedScanner));
nested_scanner_init(scanner);
return scanner;
}
// Called once parser is deleted or different language set.
// Frees memory storing scanner state.
void tree_sitter_tlaplus_external_scanner_destroy(void* const payload) {
struct NestedScanner* const scanner = (struct NestedScanner*)(payload);
nested_scanner_free(scanner);
ts_free(scanner);
}
// Called whenever this scanner recognizes a token.
// Serializes scanner state into buffer.
unsigned tree_sitter_tlaplus_external_scanner_serialize(
void* const payload,
char* const buffer
) {
const struct NestedScanner* const scanner = (struct NestedScanner*)(payload);
return nested_scanner_serialize(scanner, buffer);
}
// Called when handling edits and ambiguities.
// Deserializes scanner state from buffer.
void tree_sitter_tlaplus_external_scanner_deserialize(
void* const payload,
const char* const buffer,
unsigned const length
) {
struct NestedScanner* const scanner = (struct NestedScanner*)(payload);
nested_scanner_deserialize(scanner, buffer, length);
}
// Scans for tokens.
bool tree_sitter_tlaplus_external_scanner_scan(
void* const payload,
TSLexer* const lexer,
const bool* const valid_symbols
) {
struct NestedScanner* const scanner = (struct NestedScanner*)(payload);
return nested_scan(scanner, lexer, valid_symbols);
}