1115 lines
37 KiB
C
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;
|
|
}
|