项目文件夹

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

2038 行
75 KiB
C
已生成的

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#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);
}