add missing tag marker and error query
This commit is contained in:
1 parent
f2d3a727f6
commit
2d7d0db471
11 files changed
+34054
-19148
No files matched your search
+311
-8
@@ -1,11 +1,20 @@
|
||||
#include "tree_sitter/parser.h"
|
||||
#include "tree_sitter/alloc.h"
|
||||
#include "tree_sitter/array.h"
|
||||
#include <string.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,
|
||||
@@ -29,6 +38,14 @@ enum TokenType {
|
||||
/* 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, before the name of a tag that opens a group: push the group */
|
||||
GroupOpen,
|
||||
/* zero width, before the name of a tag that closes a group, or of the plural
|
||||
* of a counted blocktranslate: pop the group, or record its plural */
|
||||
GroupClose,
|
||||
};
|
||||
|
||||
/* The options blocktranslate takes, in the order of the bits recording them.
|
||||
@@ -47,10 +64,67 @@ typedef enum {
|
||||
BlockTag,
|
||||
PartialTag,
|
||||
VerbatimTag,
|
||||
/* The groups below have no name to match, and are kept only so that a
|
||||
* missing end tag can be told from one that belongs to the group. A counted
|
||||
* blocktranslate is a kind of its own until its plural is read, because
|
||||
* until then "plural" is the tag it is waiting for. */
|
||||
AutoescapeTag,
|
||||
BlocktransTag,
|
||||
BlocktransCountTag,
|
||||
BlocktranslateTag,
|
||||
BlocktranslateCountTag,
|
||||
CacheTag,
|
||||
CommentTag,
|
||||
FilterTag,
|
||||
ForTag,
|
||||
IfTag,
|
||||
IfchangedTag,
|
||||
LanguageTag,
|
||||
LocalizeTag,
|
||||
LocaltimeTag,
|
||||
SpacelessTag,
|
||||
TimezoneTag,
|
||||
WithTag,
|
||||
TagKindCount,
|
||||
} TagKind;
|
||||
|
||||
/* one letter per kind, in TagKind order, used to serialize an open tag */
|
||||
static const char tag_kind_chars[] = "bpv";
|
||||
static const char tag_kind_chars[] = "bpvatTrRcmfoihlzesnw";
|
||||
|
||||
typedef struct {
|
||||
/* the name of the tag GroupOpen pushes this kind for, or NULL */
|
||||
const char *open_name;
|
||||
/* The names that continue or close the group, space separated: a tag with
|
||||
* one of them belongs to the group, so is no sign its end tag is missing.
|
||||
* The last is the tag that closes it. */
|
||||
const char *own_names;
|
||||
/* The body is raw text in which no tag is read, so only the end of input
|
||||
* can show that the end tag is missing. */
|
||||
bool raw;
|
||||
} TagGroup;
|
||||
|
||||
static const TagGroup tag_groups[TagKindCount] = {
|
||||
[BlockTag] = { NULL, "endblock", false },
|
||||
[PartialTag] = { NULL, "endpartialdef", false },
|
||||
[VerbatimTag] = { NULL, "endverbatim", true },
|
||||
[AutoescapeTag] = { "autoescape", "endautoescape", false },
|
||||
[BlocktransTag] = { "blocktrans", "endblocktrans", false },
|
||||
[BlocktransCountTag] = { NULL, "plural", false },
|
||||
[BlocktranslateTag] = { "blocktranslate", "endblocktranslate", false },
|
||||
[BlocktranslateCountTag] = { NULL, "plural", false },
|
||||
[CacheTag] = { "cache", "endcache", false },
|
||||
[CommentTag] = { "comment", "endcomment", true },
|
||||
[FilterTag] = { "filter", "endfilter", false },
|
||||
[ForTag] = { "for", "empty endfor", false },
|
||||
[IfTag] = { "if", "elif else endif", false },
|
||||
[IfchangedTag] = { "ifchanged", "else endifchanged", false },
|
||||
[LanguageTag] = { "language", "endlanguage", false },
|
||||
[LocalizeTag] = { "localize", "endlocalize", false },
|
||||
[LocaltimeTag] = { "localtime", "endlocaltime", false },
|
||||
[SpacelessTag] = { "spaceless", "endspaceless", false },
|
||||
[TimezoneTag] = { "timezone", "endtimezone", false },
|
||||
[WithTag] = { "with", "endwith", false },
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
TagKind kind;
|
||||
@@ -97,7 +171,7 @@ static void reset_scanner(struct Scanner *const scanner) {
|
||||
* 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. */
|
||||
memset(scanner, 0, sizeof(*scanner));
|
||||
*scanner = (struct Scanner) {0};
|
||||
} else {
|
||||
scanner->translate_seen = 0;
|
||||
array_delete(&scanner->seen_kwargs);
|
||||
@@ -349,12 +423,18 @@ void tree_sitter_django_external_scanner_deserialize(
|
||||
}
|
||||
if (name == NULL) {
|
||||
// a tag starts with the letter for its kind
|
||||
const char *kind = code > 0 && code < 128 ? strchr(tag_kind_chars, code) : NULL;
|
||||
if (kind == NULL) {
|
||||
int kind = -1;
|
||||
for (int i = 0; i < TagKindCount; ++i) {
|
||||
if (tag_kind_chars[i] == code) {
|
||||
kind = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (kind < 0) {
|
||||
scanner_error(scanner, "Invalid tag kind");
|
||||
return;
|
||||
}
|
||||
OpenTag tag = { (TagKind) (kind - tag_kind_chars), array_new() };
|
||||
OpenTag tag = { (TagKind) kind, array_new() };
|
||||
array_push(&scanner->stack, tag);
|
||||
name = &array_back(&scanner->stack)->name;
|
||||
} else if (code == NAME_SEP) {
|
||||
@@ -460,7 +540,7 @@ static int read_translate_option(TSLexer *const lexer) {
|
||||
}
|
||||
word[size] = '\0';
|
||||
for (unsigned i = 0; i < sizeof(translate_options) / sizeof(*translate_options); ++i) {
|
||||
if (strcmp(word, translate_options[i]) == 0) {
|
||||
if (strings_equal(word, translate_options[i])) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
@@ -515,6 +595,205 @@ static bool scan_kwarg_name(struct Scanner *const scanner, TSLexer *const lexer)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* NAMES_INSIDE_A_TAG_GROUP in grammar.js, which this must be kept in step with:
|
||||
* the names reserved to a tag group, which can therefore never continue the
|
||||
* body of a group they do not belong to. */
|
||||
static const char group_names[] =
|
||||
"elif else empty endautoescape endblock endblocktrans endblocktranslate "
|
||||
"endcache endcomment endfilter endfor endif endifchanged endlanguage "
|
||||
"endlocalize endlocaltime endpartialdef endspaceless endtimezone endverbatim "
|
||||
"endwith plural";
|
||||
|
||||
/* The longest of group_names and of the opening names, "endblocktranslate",
|
||||
* is 17. */
|
||||
#define GROUP_NAME_MAX 17
|
||||
|
||||
/* Whether `word` is one of the space-separated names in `list`. */
|
||||
static bool name_in_list(const char *const word, const char *list) {
|
||||
for (;;) {
|
||||
const char *letter = word;
|
||||
while (*letter != '\0' && *letter == *list) {
|
||||
++letter;
|
||||
++list;
|
||||
}
|
||||
if (*letter == '\0' && (*list == ' ' || *list == '\0')) {
|
||||
return true;
|
||||
}
|
||||
while (*list != ' ' && *list != '\0') {
|
||||
++list;
|
||||
}
|
||||
if (*list == '\0') {
|
||||
return false;
|
||||
}
|
||||
++list;
|
||||
}
|
||||
}
|
||||
|
||||
/* The last of the space-separated names in `list`. */
|
||||
static const char *last_name(const char *list) {
|
||||
const char *last = list;
|
||||
for (; *list != '\0'; ++list) {
|
||||
if (*list == ' ') {
|
||||
last = list + 1;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Reads a tag's name into `word`, and reports whether it could be the name of
|
||||
* a group's tag at all: one longer than any of them is not. */
|
||||
static bool read_group_word(TSLexer *const lexer, char word[GROUP_NAME_MAX + 1]) {
|
||||
unsigned size = 0;
|
||||
while (check_name_char(lexer->lookahead)) {
|
||||
if (size < GROUP_NAME_MAX) {
|
||||
word[size] = (char) lexer->lookahead;
|
||||
}
|
||||
++size;
|
||||
lexer->advance(lexer, false);
|
||||
}
|
||||
if (size == 0 || size > GROUP_NAME_MAX) {
|
||||
return false;
|
||||
}
|
||||
word[size] = ' | ||||