Files
tree-sitter-django/src/scanner.c
T
2026-09-14 21:40:26 -05:00

1115 lines
37 KiB
C

#include "tree_sitter/parser.h"
#include "tree_sitter/alloc.h"
#include "tree_sitter/array.h"
#define ERROR_SIZE 64
#define NAME_SEP ' '
/* A scanner is also built for Wasm, where only a few C library functions are
* available and string.h is not among them, so strings are compared here. */
static bool strings_equal(const char *left, const char *right) {
while (*left != '\0' && *left == *right) {
++left;
++right;
}
return *left == *right;
}
enum TokenType {
/* indicates that an error occurred */
MatcherError,
/* check a close tag's name against the innermost open group */
PopBlock, // [ name ] %}
PopPartial, // [ name ] %}
PopVerbatim, // [ name ] %}
/* read the name an opening tag gives its group */
PushBlock, // name %}
PushPartial, // name [ inline ] %}
PushVerbatim, // [ name ] %}
/* raw text inside a verbatim block, up through (but excluding) the matching endverbatim tag */
VerbatimContent,
/* raw text inside a comment block, up through (but excluding) the endcomment tag */
CommentContent,
/* zero width, at the start of a tag: forget what the tag before it held */
TagOpen,
/* zero width, before an option of a blocktranslate tag: record it, and fail
* if that option has already been written once */
TranslateOption,
/* zero width, before the name of a "name=value" argument: record the name,
* and fail if the tag has already been given one by that name */
KwargName,
/* zero width, where a tag group's required tag is missing; see
* scan_missing_tag */
MissingTag,
/* zero width, after "{%": read the tag's name */
GroupOpenTagRead,
/* zero width, before the "%}" of a tag that opens a group: push the group */
GroupOpenTagPush,
/* zero width, before the "%}" of a middle tag a group holds once, such as
* "else": record that the group holds it */
GroupFollow,
/* zero width, before the "%}" of a middle tag a group may hold any number of
* times, such as "elif" */
GroupRepeat,
/* zero width, before the "%}" of a tag that closes a group: pop the group */
GroupClose,
/* zero width, before the "%}" of an unexpected_tag: returned only when
* the innermost group already holds a tag by that name, which makes this the
* likelier stray of the two */
GroupHeld,
/* a "{" that is text although another "{" follows it, because the second
* one opens a tag or a comment, or although nothing follows it at all */
TextBrace,
};
/* The options blocktranslate takes, in the order of the bits recording them.
* do_block_translate keeps the ones it has seen in a dict and refuses a repeat,
* so a bit per option is the whole of the state this needs. */
static const char *const translate_options[] = {
"with", "count", "context", "trimmed", "asvar",
};
/* the index of "count" in translate_options */
#define TRANSLATE_COUNT 1
typedef Array(int32_t) Name;
/* An open tag group. The scanner knows nothing about any particular group: the
* grammar reads each tag's name to it, and every group is closed by "end"
* followed by the name of the tag that opened it. */
typedef struct {
/* the name of the tag that closes the group */
Name expected;
/* the name a block, partialdef or verbatim tag gave the group, or empty */
Name name;
/* the middle tags the group may hold only once and already holds, such as
* "else", each followed by NAME_SEP */
Name middles;
/* a counted blocktranslate, whose plural is still to come */
bool awaits_plural;
} OpenGroup;
/* The open groups, innermost last. The grammar nests them, so only the
* innermost can be closed. */
typedef Array(OpenGroup) Stack;
struct Scanner {
bool has_error;
/* Everything below is readable only while has_error is false: the error
* string shares its storage with all of it.
*
* translate_seen holds one bit per option of the blocktranslate tag being
* read, by the order of translate_options. seen_kwargs holds the
* "name=value" argument names the tag has been given, each followed by
* NAME_SEP: parse_bits refuses a repeated keyword argument and the set of
* names is open, so the names are kept rather than a mask of known ones.
*
* read_word is the name of the tag being read, pending_name the name its
* push token read (for block, partialdef and verbatim), and pending_plural
* whether it is a counted blocktranslate; all three wait for the tag's end,
* where GroupOpenTagPush turns them into an open group. */
union {
struct {
uint8_t translate_seen;
bool pending_plural;
Name seen_kwargs;
Name read_word;
Name pending_name;
Stack stack;
};
char error [ERROR_SIZE];
};
};
static void delete_group(OpenGroup *const group) {
array_delete(&group->expected);
array_delete(&group->name);
array_delete(&group->middles);
}
static void pop_group(struct Scanner *const scanner) {
delete_group(array_back(&scanner->stack));
array_pop(&scanner->stack);
}
static void reset_scanner(struct Scanner *const scanner) {
if (scanner->has_error) {
/* The error shares its storage with the state below, so none of that is
* readable here and none of it holds a pointer to free: scanner_error
* resets before it sets has_error, which is what frees them. Zeroing the
* whole struct clears the error and the state it overlays at once. */
*scanner = (struct Scanner) {0};
} else {
scanner->translate_seen = 0;
scanner->pending_plural = false;
array_delete(&scanner->seen_kwargs);
array_delete(&scanner->read_word);
array_delete(&scanner->pending_name);
for (unsigned i = 0; i < scanner->stack.size; ++i) {
delete_group(array_get(&scanner->stack, i));
}
array_delete(&scanner->stack);
}
}
/* The size of a name copied into a log message, its terminator included. */
#define LOG_NAME_SIZE 64
/* Copies a name into `out` for a log message, cut short to fit it. A name holds
* only ASCII letters, digits and underscores, so each code point is one char. */
static const char *log_name(const Name *const name, char out[LOG_NAME_SIZE]) {
unsigned i = 0;
for (; i < name->size && i < LOG_NAME_SIZE - 1; ++i) {
out[i] = (char) name->contents[i];
}
out[i] = '\0';
return out;
}
/* Copies `string` into the error, cut short to fit it. */
static void set_error(struct Scanner *const scanner, const char *const string) {
unsigned i = 0;
for (; i < ERROR_SIZE - 1 && string[i] != '\0'; ++i) {
scanner->error[i] = string[i];
}
scanner->error[i] = '\0';
}
#define scanner_error(scanner, string) do {\
struct Scanner *_scanner = scanner;\
reset_scanner(_scanner);\
_scanner->has_error = true;\
set_error(_scanner, string);\
} while(0)
void * tree_sitter_django_external_scanner_create() {
return ts_calloc(1, sizeof(struct Scanner));
}
void tree_sitter_django_external_scanner_destroy(void *payload) {
struct Scanner *scanner = (struct Scanner*) payload;
reset_scanner(scanner);
ts_free(payload);
}
static unsigned write_code(int32_t code, char *out) {
if (code <= 0x7F) {
// 1 byte: 0xxxxxxx
out[0] = code;
return 1;
}
else if (code <= 0x7FF) {
// 2 bytes: 110xxxxx 10xxxxxx
out[0] = (code >> 6) | 0xC0;
out[1] = (code & 0x3F) | 0x80;
return 2;
}
else if (code <= 0xFFFF) {
// 3 bytes: 1110xxxx 10xxxxxx 10xxxxxx
// Note: Code points U+D800 to U+DFFF are reserved for UTF-16 surrogates and are invalid
if (code >= 0xD800 && code <= 0xDFFF) {
return 0;
}
out[0] = (code >> 12) | 0xE0;
out[1] = ((code >> 6) & 0x3F) | 0x80;
out[2] = (code & 0x3F) | 0x80;
return 3;
}
else if (code <= 0x10FFFF) {
// 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
out[0] = (code >> 18) | 0xF0;
out[1] = ((code >> 12) & 0x3F) | 0x80;
out[2] = ((code >> 6) & 0x3F) | 0x80;
out[3] = (code & 0x3F) | 0x80;
return 4;
}
return 0; // Out of Unicode range
}
/* Writes one code point, or reports that it is invalid or would not fit before
* `end`: a stack of open groups holds whole names, so a deep enough nesting
* outgrows the buffer tree-sitter serializes into. */
static bool write_checked(const int32_t code, char **const iter, const char *const end) {
if (end - *iter < 4) {
return false;
}
const unsigned write_amt = write_code(code, *iter);
if (write_amt == 0) {
return false;
}
*iter += write_amt;
return true;
}
static bool write_name(const Name *const name, char **const iter, const char *const end) {
for (unsigned i = 0; i < name->size; ++i) {
if (!write_checked(name->contents[i], iter, end)) {
return false;
}
}
return true;
}
/* The state is written as:
*
* status "0", or "1" for an error state, which is followed by the error
* mask translate_seen, as a character from '0'
* plural pending_plural, '0' or '1'
* seen_kwargs "\n" read_word "\n" pending_name "\n"
* then per open group, outermost first:
* expected ":" name ":" middles ":" awaits_plural "\n"
*
* No name holds a newline or a colon, so neither needs escaping. */
unsigned tree_sitter_django_external_scanner_serialize(
void *payload,
char *const buffer
) {
struct Scanner *scanner = (struct Scanner*) payload;
const char *const end = buffer + TREE_SITTER_SERIALIZATION_BUFFER_SIZE;
write_serialization_error:;
char *iter = buffer;
if (scanner->has_error) {
*(iter++) = '1';
for (int i = 0; i < ERROR_SIZE; ++i) {
char value = scanner->error[i];
*(iter++) = value;
if (value == '\0') {
break;
}
}
return iter - buffer;
}
bool written = write_checked('0', &iter, end)
// five options fit in five bits, which stays inside printable ASCII
&& write_checked(scanner->translate_seen + '0', &iter, end)
&& write_checked(scanner->pending_plural ? '1' : '0', &iter, end)
&& write_name(&scanner->seen_kwargs, &iter, end)
&& write_checked('\n', &iter, end)
&& write_name(&scanner->read_word, &iter, end)
&& write_checked('\n', &iter, end)
&& write_name(&scanner->pending_name, &iter, end)
&& write_checked('\n', &iter, end);
for (unsigned i = 0; written && i < scanner->stack.size; ++i) {
const OpenGroup *const group = array_get(&scanner->stack, i);
written = write_name(&group->expected, &iter, end)
&& write_checked(':', &iter, end)
&& write_name(&group->name, &iter, end)
&& write_checked(':', &iter, end)
&& write_name(&group->middles, &iter, end)
&& write_checked(':', &iter, end)
&& write_checked(group->awaits_plural ? '1' : '0', &iter, end)
&& write_checked('\n', &iter, end);
}
if (!written) {
scanner_error(scanner, "the open groups do not fit the serialization buffer");
goto write_serialization_error;
}
return iter - buffer;
}
static unsigned read_code(const char *iter, int32_t *result) {
unsigned char byte0 = iter[0];
if (byte0 <= 0x7F) {
// 1 byte: 0xxxxxxx
*result = byte0;
return 1;
} else if ((byte0 & 0xE0) == 0xC0) {
// 2 bytes: 110xxxxx 10xxxxxx
*result = ((byte0 & 0x1F) << 6) | (iter[1] & 0x3F);
return 2;
} else if ((byte0 & 0xF0) == 0xE0) {
// 3 bytes: 1110xxxx 10xxxxxx 10xxxxxx
*result = ((byte0 & 0x0F) << 12) | ((iter[1] & 0x3F) << 6) | (iter[2] & 0x3F);
return 3;
} else if ((byte0 & 0xF8) == 0xF0) {
// 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*result = ((byte0 & 0x07) << 18) | ((iter[1] & 0x3F) << 12) | ((iter[2] & 0x3F) << 6) | (iter[3] & 0x3F);
return 4;
}
return 0; // invalid UTF-8 leading byte
}
/* The characters SEP matches in the grammar: whitespace separating the parts
* of a tag, a newline among them. */
static bool check_space(const int32_t character) {
return character == ' ' || character == '\t'
|| character == '\n' || character == '\r';
}
static bool check_name_start_char(const int32_t letter) {
return 'a' <= letter && letter <= 'z' || 'A' <= letter && letter <= 'Z';
}
static bool check_name_char(const int32_t letter) {
return check_name_start_char(letter) || '0' <= letter && letter <= '9' || letter == '_';
}
/* Reads one code point of the serialized state, or reports that none is left. */
static bool read_checked(const char **const iter, const char *const end, int32_t *const code) {
if (*iter >= end) {
return false;
}
const unsigned bytes_read = read_code(*iter, code);
if (bytes_read == 0 || (unsigned) (end - *iter) < bytes_read) {
return false;
}
*iter += bytes_read;
return true;
}
/* Reads name characters, and NAME_SEP where `separated`, up to `terminator`. */
static bool read_field(
const char **const iter,
const char *const end,
Name *const out,
const int32_t terminator,
const bool separated
) {
int32_t code;
for (;;) {
if (!read_checked(iter, end, &code)) {
return false;
}
if (code == terminator) {
return true;
}
if (!check_name_char(code) && !(separated && code == NAME_SEP)) {
return false;
}
array_push(out, code);
}
}
void tree_sitter_django_external_scanner_deserialize(
void *payload,
const char *buffer,
unsigned length
) {
struct Scanner *scanner = (struct Scanner*) payload;
reset_scanner(scanner);
if (length == 0) {
return;
}
const char *iter = buffer;
const char *const end = buffer + length;
int32_t status;
if (!read_checked(&iter, end, &status)) {
scanner_error(scanner, "received invalid utf8 byte");
return;
}
if (status == '1') {
scanner->has_error = true;
unsigned i = 0;
for (; i < ERROR_SIZE - 1 && iter < end && *iter != '\0'; ++i) {
scanner->error[i] = *(iter++);
}
scanner->error[i] = '\0';
return;
}
int32_t mask;
int32_t plural;
if (status != '0' || !read_checked(&iter, end, &mask) || !read_checked(&iter, end, &plural)) {
scanner_error(scanner, "received unrecognized scanner status");
return;
}
if (mask < '0' || mask > '0' + 0x1F || (plural != '0' && plural != '1')) {
scanner_error(scanner, "received unrecognized option mask");
return;
}
scanner->translate_seen = (uint8_t) (mask - '0');
scanner->pending_plural = plural == '1';
if (!read_field(&iter, end, &scanner->seen_kwargs, '\n', true)
|| !read_field(&iter, end, &scanner->read_word, '\n', false)
|| !read_field(&iter, end, &scanner->pending_name, '\n', false)) {
scanner_error(scanner, "received invalid tag names");
return;
}
while (iter < end) {
OpenGroup group = { array_new(), array_new(), array_new(), false };
int32_t awaits_plural;
int32_t newline;
const bool read = read_field(&iter, end, &group.expected, ':', false)
&& read_field(&iter, end, &group.name, ':', false)
&& read_field(&iter, end, &group.middles, ':', true)
&& read_checked(&iter, end, &awaits_plural)
&& (awaits_plural == '0' || awaits_plural == '1')
&& read_checked(&iter, end, &newline)
&& newline == '\n';
if (!read) {
delete_group(&group);
scanner_error(scanner, "received an invalid open group");
return;
}
group.awaits_plural = awaits_plural == '1';
array_push(&scanner->stack, group);
}
}
static bool read_name(TSLexer *const lexer, Name *const name) {
if (name->size == 0) {
if (!check_name_start_char(lexer->lookahead)) {
return false;
}
array_push(name, lexer->lookahead);
lexer->advance(lexer, false);
}
while (check_name_char(lexer->lookahead)) {
array_push(name, lexer->lookahead);
lexer->advance(lexer, false);
}
return true;
}
static unsigned check_name(TSLexer *const lexer, const Name *const name) {
for (unsigned i = 0; i < name->size; ++i) {
if (lexer->lookahead != name->contents[i]) {
return i;
}
lexer->advance(lexer, false);
}
return name->size;
}
static bool check_close_tag(TSLexer *const lexer) {
while (check_space(lexer->lookahead)) {
lexer->advance(lexer, false);
}
if (lexer->lookahead != '%') {
return false;
}
lexer->advance(lexer, false);
return lexer->lookahead == '}';
}
/* Marks the name just read as the token, then checks that only "%}" follows. */
static bool check_close_tag_from(TSLexer *const lexer) {
lexer->mark_end(lexer);
return check_close_tag(lexer);
}
const char inline_chars[] = "inline";
static bool check_inline(TSLexer *const lexer) {
for (unsigned i = 0; i < sizeof(inline_chars) - 1; ++i) {
if (lexer->lookahead != inline_chars[i]) {
return false;
}
lexer->advance(lexer, false);
}
return check_close_tag(lexer);
}
static void skip_whitespace(TSLexer *const lexer) {
while (check_space(lexer->lookahead)) {
lexer->advance(lexer, false);
}
}
/* The longest name in translate_options, "context" and "trimmed", is 7. */
#define TRANSLATE_OPTION_MAX 8
/* Reads the word naming a blocktranslate option and returns its index, or -1
* for anything else, which includes the end of the tag. Consumes nothing the
* caller keeps: it is called after the end has been marked. */
static int read_translate_option(TSLexer *const lexer) {
char word[TRANSLATE_OPTION_MAX + 1];
unsigned size = 0;
if (!check_name_start_char(lexer->lookahead)) {
return -1;
}
while (check_name_char(lexer->lookahead)) {
if (size < TRANSLATE_OPTION_MAX) {
word[size] = (char) lexer->lookahead;
}
++size;
lexer->advance(lexer, false);
}
if (size > TRANSLATE_OPTION_MAX) {
return -1;
}
word[size] = '\0';
for (unsigned i = 0; i < sizeof(translate_options) / sizeof(*translate_options); ++i) {
if (strings_equal(word, translate_options[i])) {
return (int) i;
}
}
return -1;
}
/* Scans TranslateOption once its whitespace is skipped and the end marked. */
static bool scan_translate_option(struct Scanner *const scanner, TSLexer *const lexer) {
const int option = read_translate_option(lexer);
if (option < 0) {
return false;
}
const uint8_t bit = (uint8_t) (1 << option);
if (scanner->translate_seen & bit) {
lexer->log(lexer, "refused blocktranslate option %s: it is already written once in this tag",
translate_options[option]);
return false;
}
scanner->translate_seen |= bit;
if (option == TRANSLATE_COUNT) {
// the group this tag pushes waits for a plural before its end tag
scanner->pending_plural = true;
}
lexer->result_symbol = TranslateOption;
return true;
}
/* Reads the name of a "name=value" argument, and reports whether the tag can
* still take it. A name not followed by "=" is not one of these arguments at
* all, which is what tells this apart from the "as name" clause or the end of
* the tag; the name is only kept once the "=" has been seen.
*
* The names are held end to end, each followed by NAME_SEP, which no name
* holds. The candidate is appended first and dropped again if it turns out to
* be a repeat, so nothing is kept unless the token is returned. */
static bool scan_kwarg_name(struct Scanner *const scanner, TSLexer *const lexer) {
if (!check_name_start_char(lexer->lookahead)) {
return false;
}
const unsigned start = scanner->seen_kwargs.size;
while (check_name_char(lexer->lookahead)) {
array_push(&scanner->seen_kwargs, lexer->lookahead);
lexer->advance(lexer, false);
}
const unsigned size = scanner->seen_kwargs.size - start;
if (lexer->lookahead != '=') {
scanner->seen_kwargs.size = start;
return false;
}
for (unsigned i = 0; i < start;) {
unsigned end = i;
while (end < start && *array_get(&scanner->seen_kwargs, end) != NAME_SEP) {
++end;
}
if (end - i == size) {
bool same = true;
for (unsigned j = 0; j < size; ++j) {
if (*array_get(&scanner->seen_kwargs, i + j)
!= *array_get(&scanner->seen_kwargs, start + j)) {
same = false;
break;
}
}
if (same) {
const Name name = { array_get(&scanner->seen_kwargs, start), size, size };
char buffer[LOG_NAME_SIZE];
lexer->log(lexer, "refused keyword argument %s: the tag already holds one by that name",
log_name(&name, buffer));
scanner->seen_kwargs.size = start;
return false;
}
}
i = end + 1;
}
array_push(&scanner->seen_kwargs, NAME_SEP);
return true;
}
static bool names_equal(const Name *const left, const Name *const right) {
if (left->size != right->size) {
return false;
}
for (unsigned i = 0; i < left->size; ++i) {
if (left->contents[i] != right->contents[i]) {
return false;
}
}
return true;
}
static bool name_is(const Name *const name, const char *const string) {
unsigned i = 0;
for (; i < name->size; ++i) {
if (string[i] == '\0' || name->contents[i] != string[i]) {
return false;
}
}
return string[i] == '\0';
}
/* Whether `word` is one of the names in `list`, each followed by NAME_SEP. */
static bool name_in_list(const Name *const word, const Name *const list) {
unsigned start = 0;
for (unsigned i = 0; i < list->size; ++i) {
if (list->contents[i] != NAME_SEP) {
continue;
}
if (i - start == word->size) {
bool same = true;
for (unsigned j = 0; j < word->size; ++j) {
if (list->contents[start + j] != word->contents[j]) {
same = false;
break;
}
}
if (same) {
return true;
}
}
start = i + 1;
}
return false;
}
/* Whether a tag named `word` closes a group enclosing the innermost one. */
static bool outer_group_expects(const struct Scanner *const scanner, const Name *const word) {
for (unsigned i = scanner->stack.size - 1; i-- > 0;) {
if (names_equal(&array_get(&scanner->stack, i)->expected, word)) {
return true;
}
}
return false;
}
/* Reads a tag's name, which may be empty. */
static void read_word(TSLexer *const lexer, Name *const word) {
while (check_name_char(lexer->lookahead)) {
array_push(word, lexer->lookahead);
lexer->advance(lexer, false);
}
}
/* Scans GroupOpenTagRead, zero width after "{%": records the name of the tag
* that follows for the tokens at its end, and forgets what the tag before it
* left there. It is valid before every tag's name, so it is always returned. */
static void scan_group_open_tag_read(struct Scanner *const scanner, TSLexer *const lexer) {
lexer->mark_end(lexer);
array_clear(&scanner->read_word);
array_clear(&scanner->pending_name);
scanner->pending_plural = false;
skip_whitespace(lexer);
read_word(lexer, &scanner->read_word);
lexer->result_symbol = GroupOpenTagRead;
}
/* Scans GroupOpenTagPush, GroupFollow, GroupRepeat, GroupHeld or GroupClose,
* once "%}" has been found to follow. */
static bool scan_group_tag_end(
struct Scanner *const scanner,
TSLexer *const lexer,
const bool *const valid_symbols
) {
char buffer[LOG_NAME_SIZE];
if (valid_symbols[GroupOpenTagPush]) {
OpenGroup group = {
array_new(), scanner->pending_name, array_new(), scanner->pending_plural,
};
scanner->pending_name = (Name) array_new();
scanner->pending_plural = false;
array_push(&group.expected, 'e');
array_push(&group.expected, 'n');
array_push(&group.expected, 'd');
for (unsigned i = 0; i < scanner->read_word.size; ++i) {
array_push(&group.expected, scanner->read_word.contents[i]);
}
array_push(&scanner->stack, group);
lexer->result_symbol = GroupOpenTagPush;
return true;
}
if (valid_symbols[GroupFollow]) {
if (scanner->stack.size == 0) {
lexer->log(lexer, "not recording %s: the parser is inside a group, but the stack holds none",
log_name(&scanner->read_word, buffer));
} else {
OpenGroup *const group = array_back(&scanner->stack);
if (name_in_list(&scanner->read_word, &group->middles)) {
// a second one of these parses as an unexpected_tag, which has no follow
lexer->log(lexer, "refused %s: the parser gives it to the innermost group, which already holds one",
log_name(&scanner->read_word, buffer));
return false;
}
for (unsigned i = 0; i < scanner->read_word.size; ++i) {
array_push(&group->middles, scanner->read_word.contents[i]);
}
array_push(&group->middles, NAME_SEP);
if (name_is(&scanner->read_word, "plural")) {
group->awaits_plural = false;
}
}
lexer->result_symbol = GroupFollow;
return true;
}
if (valid_symbols[GroupRepeat]) {
lexer->result_symbol = GroupRepeat;
return true;
}
if (valid_symbols[GroupHeld]) {
if (scanner->stack.size == 0
|| !name_in_list(&scanner->read_word, &array_back(&scanner->stack)->middles)) {
return false;
}
lexer->result_symbol = GroupHeld;
return true;
}
if (!valid_symbols[GroupClose]) {
return false;
}
if (scanner->stack.size == 0) {
lexer->log(lexer, "refused %s: the parser expects a group to close, but the stack holds none",
log_name(&scanner->read_word, buffer));
return false;
}
const OpenGroup *const group = array_back(&scanner->stack);
if (!names_equal(&group->expected, &scanner->read_word) || group->awaits_plural) {
char expected[LOG_NAME_SIZE];
lexer->log(lexer, "refused %s: the parser expects a group to close, but the innermost on the stack %s %s",
log_name(&scanner->read_word, buffer),
group->awaits_plural ? "still waits for the plural before" : "closes with",
log_name(&group->expected, expected));
return false;
}
pop_group(scanner);
lexer->result_symbol = GroupClose;
return true;
}
typedef enum {
/* no marker here, and nothing has been read */
NotMissing,
/* the marker was scanned */
Missing,
/* no marker, but input was read to decide that, so nothing else may be
* scanned from here */
NotMissingAfterReading,
/* no marker, but the "{" read to decide that was text, and was scanned */
TextBraceScanned,
} MissingResult;
/* Scans TextBrace once its "{" has been read. Django's lexer takes a template
* apart with ({%.*?%}|{{.*?}}|{#.*?#}), so a "{" is text unless one of those
* starts at it: before "{%" or "{#" the tag or comment starts one character
* later, and at the end of input nothing starts at all. The content regex
* cannot look that far ahead, so these are scanned here; "{{{" is not one of
* them, since "{{" does start a variable at its first brace. */
static bool scan_text_brace_rest(TSLexer *const lexer) {
lexer->mark_end(lexer);
if (lexer->eof(lexer)) {
lexer->result_symbol = TextBrace;
return true;
}
if (lexer->lookahead != '{') {
return false;
}
lexer->advance(lexer, false);
if (lexer->lookahead == '%' || lexer->lookahead == '#') {
lexer->result_symbol = TextBrace;
return true;
}
return false;
}
/* Scans the zero-width marker for the innermost group's missing tag. The
* marker is valid only where that group could close, which is after a body,
* where nothing else of this scanner is valid but the raw-text tokens, and
* those are scanned only when this returns NotMissing.
*
* The tag is taken to be missing at the end of input; before the end tag of a
* group enclosing the innermost one, or an endblock or endpartialdef naming a
* block further out; and before a second plural, since a blocktranslate body
* holds no tag that could take it. Any other tag is taken to be the innermost
* group's, as Django takes it: a second of a tag the group holds only once, such
* as a second "else", is an unexpected_tag inside the group rather than a sign
* that it has ended. A counted blocktranslate still waiting for its plural is
* missing it before anything but "plural". The marker closes the group as the
* missing tag would have, or for a plural records it, leaving the group open. */
static MissingResult scan_missing_tag(
struct Scanner *const scanner,
TSLexer *const lexer,
const bool *const valid_symbols
) {
if (scanner->stack.size == 0) {
// the marker is only valid inside a group, so the stack has lost track of it
lexer->log(lexer, "no missing-tag marker: the parser is inside a group, but the stack holds none");
return NotMissing;
}
OpenGroup *const innermost = array_back(&scanner->stack);
lexer->mark_end(lexer);
if (!lexer->eof(lexer)) {
// tags are not read in a raw body, so only the end of input shows its end is missing
const bool raw = valid_symbols[VerbatimContent] || valid_symbols[CommentContent];
if (raw || lexer->lookahead != '{') {
return NotMissing;
}
lexer->advance(lexer, false);
if (lexer->lookahead != '%') {
// not a tag, but the "{" may be text the grammar still has to take
if (valid_symbols[TextBrace] && scan_text_brace_rest(lexer)) {
return TextBraceScanned;
}
return NotMissingAfterReading;
}
lexer->advance(lexer, false);
skip_whitespace(lexer);
Name word = array_new();
read_word(lexer, &word);
bool missing = false;
if (word.size == 0) {
missing = false;
} else if (innermost->awaits_plural) {
missing = !name_is(&word, "plural")
&& (names_equal(&innermost->expected, &word) || outer_group_expects(scanner, &word));
} else if (names_equal(&innermost->expected, &word)) {
// the group's own end tag, unless it names a block further out
if (innermost->name.size > 0 && check_space(lexer->lookahead) && scanner->stack.size > 1) {
skip_whitespace(lexer);
Name name = array_new();
if (read_name(lexer, &name) && !names_equal(&innermost->name, &name)) {
for (unsigned i = scanner->stack.size - 1; i-- > 0;) {
const OpenGroup *const group = array_get(&scanner->stack, i);
if (names_equal(&group->expected, &word) && names_equal(&group->name, &name)) {
missing = true;
break;
}
}
}
array_delete(&name);
}
} else if (name_in_list(&word, &innermost->middles)) {
// a second of a tag the group holds only once is an unexpected_tag in it
missing = name_is(&word, "plural");
} else {
missing = outer_group_expects(scanner, &word);
}
array_delete(&word);
if (!missing) {
return NotMissingAfterReading;
}
}
if (innermost->awaits_plural) {
innermost->awaits_plural = false;
} else {
pop_group(scanner);
}
lexer->result_symbol = MissingTag;
return Missing;
}
/* Called right after consuming "{%"; checks whether what follows is the tag
* that closes `group`: its end tag, with the group's name if it has one. Consumes
* input either way, since a failed match still belongs in the raw text. */
static bool check_group_close(TSLexer *const lexer, const OpenGroup *const group) {
skip_whitespace(lexer);
if (check_name(lexer, &group->expected) != group->expected.size) {
return false;
}
skip_whitespace(lexer);
if (group->name.size > 0) {
if (check_name(lexer, &group->name) != group->name.size || check_name_char(lexer->lookahead)) {
return false;
}
skip_whitespace(lexer);
}
if (lexer->lookahead != '%') {
return false;
}
lexer->advance(lexer, false);
return lexer->lookahead == '}';
}
/* Consumes the raw body of the innermost group, verbatim or comment, up to
* (but not including) the tag that closes it, since that text must not be
* parsed as template syntax. A verbatim block opened with a name is only closed
* by an "endverbatim" tag bearing the same name, which lets an unrelated (e.g.
* unnamed) "{% verbatim %}...{% endverbatim %}" pair appear inside it. */
static bool scan_raw_content(
struct Scanner *const scanner,
TSLexer *const lexer,
const enum TokenType token
) {
if (scanner->stack.size == 0) {
return false;
}
const OpenGroup *const group = array_back(&scanner->stack);
bool consumed_any = false;
while (true) {
if (lexer->eof(lexer)) {
if (!consumed_any) {
return false;
}
lexer->mark_end(lexer);
lexer->result_symbol = token;
return true;
}
if (lexer->lookahead == '{') {
lexer->mark_end(lexer);
lexer->advance(lexer, false);
if (lexer->lookahead == '%') {
lexer->advance(lexer, false);
if (check_group_close(lexer, group)) {
if (!consumed_any) {
return false;
}
lexer->result_symbol = token;
return true;
}
}
consumed_any = true;
continue;
}
lexer->advance(lexer, false);
consumed_any = true;
}
}
bool tree_sitter_django_external_scanner_scan(
void *payload,
TSLexer *lexer,
const bool *valid_symbols
) {
struct Scanner *scanner = (struct Scanner*) payload;
if (scanner->has_error) {
lexer->log(lexer, "%s", scanner->error);
if (valid_symbols[MatcherError]) {
// scanner is an error state, flag problem by returning error token
lexer->result_symbol = MatcherError;
return true;
}
return false;
}
if (valid_symbols[MatcherError]) {
return false;
}
/* The zero-width tokens below belong here: above the raw-text scanners,
* which find nothing at the end of an empty body, above the loop further
* down, which skips the separator the grammar still has to match, and below
* the MatcherError check, which keeps them from being scanned during error
* recovery. */
if (valid_symbols[MissingTag]) {
switch (scan_missing_tag(scanner, lexer, valid_symbols)) {
case Missing:
case TextBraceScanned:
return true;
case NotMissingAfterReading:
return false;
case NotMissing:
break;
}
}
if (valid_symbols[TextBrace] && lexer->lookahead == '{') {
lexer->advance(lexer, false);
return scan_text_brace_rest(lexer);
}
if (valid_symbols[GroupOpenTagRead]) {
scan_group_open_tag_read(scanner, lexer);
return true;
}
if (valid_symbols[GroupOpenTagPush] || valid_symbols[GroupFollow]
|| valid_symbols[GroupRepeat] || valid_symbols[GroupClose]
|| valid_symbols[GroupHeld]) {
lexer->mark_end(lexer);
const bool spaced = check_space(lexer->lookahead);
skip_whitespace(lexer);
if (lexer->lookahead == '%') {
lexer->advance(lexer, false);
return lexer->lookahead == '}' && scan_group_tag_end(scanner, lexer, valid_symbols);
}
// a blocktranslate option may be written where its tag could also end
if (spaced && valid_symbols[TranslateOption]) {
return scan_translate_option(scanner, lexer);
}
return false;
}
if (valid_symbols[VerbatimContent]) {
return scan_raw_content(scanner, lexer, VerbatimContent);
}
if (valid_symbols[CommentContent]) {
return scan_raw_content(scanner, lexer, CommentContent);
}
if (valid_symbols[TagOpen]) {
lexer->mark_end(lexer);
scanner->translate_seen = 0;
array_delete(&scanner->seen_kwargs);
lexer->result_symbol = TagOpen;
return true;
}
if (valid_symbols[KwargName]) {
lexer->mark_end(lexer);
if (!scan_kwarg_name(scanner, lexer)) {
return false;
}
lexer->result_symbol = KwargName;
return true;
}
if (valid_symbols[TranslateOption]) {
lexer->mark_end(lexer);
if (!check_space(lexer->lookahead)) {
return false;
}
skip_whitespace(lexer);
return scan_translate_option(scanner, lexer);
}
while (check_space(lexer->lookahead)) {
lexer->advance(lexer, true);
}
bool is_empty = false;
if (lexer->lookahead == '%') {
lexer->mark_end(lexer);
lexer->advance(lexer, false);
if (lexer->lookahead != '}') {
return false;
}
is_empty = true;
}
/* A close tag's name is checked against the innermost open group, the only
* one it can close; GroupClose, at the end of the tag, is what pops it. */
for (unsigned token = PopBlock; token <= PopVerbatim; ++token) {
if (!valid_symbols[token]) {
continue;
}
char buffer[LOG_NAME_SIZE];
if (scanner->stack.size == 0) {
lexer->log(lexer, "refused a close tag's name: the parser expects a group to close, but the stack holds none");
return false;
}
const OpenGroup *const group = array_back(&scanner->stack);
if (is_empty) {
// a close tag without a name closes the innermost group whatever its name
lexer->result_symbol = token;
return true;
}
const unsigned match_amt = check_name(lexer, &group->name);
const bool has_next_char = group->name.size ? check_name_char(lexer->lookahead) : check_name_start_char(lexer->lookahead);
if (match_amt == group->name.size && !has_next_char && check_close_tag_from(lexer)) {
lexer->result_symbol = token;
return true;
}
char expected[LOG_NAME_SIZE];
lexer->log(lexer, "refused %s: it does not name the innermost open \"%s\"",
log_name(&group->expected, expected), log_name(&group->name, buffer));
return false;
}
for (unsigned token = PushBlock; token <= PushVerbatim; ++token) {
if (!valid_symbols[token]) {
continue;
}
if (is_empty && token == PushVerbatim) {
// an unnamed verbatim block, whose group GroupOpenTagPush pushes nameless
array_clear(&scanner->pending_name);
lexer->result_symbol = token;
return true;
}
Name name = array_new();
if (read_name(lexer, &name)) {
// validate tokens after name
lexer->mark_end(lexer);
if (check_close_tag(lexer) || token == PushPartial && check_inline(lexer)) {
array_delete(&scanner->pending_name);
scanner->pending_name = name;
lexer->result_symbol = token;
return true;
}
}
array_delete(&name);
// single failed push implies failure to match any push
return false;
}
return false;
}