#include "tag.h"
#include "tree_sitter/parser.h"
#include <stdint.h>
#include <string.h>
#include <wctype.h>
enum TokenType {
AUTOMATIC_SEMICOLON,
TERNARY_QMARK,
ELVIS_OPERATOR,
LOGICAL_OR,
CF_START_TAG_NAME,
CF_END_TAG_NAME,
ERRONEOUS_CF_END_TAG_NAME,
CF_SELF_CLOSING_TAG_DELIMITER,
CF_SELF_CLOSING_VOID_TAG_DELIMITER,
IMPLICIT_CF_END_TAG,
RAW_TEXT,
CFML_COMMENT,
CLOSE_TAG_DELIM,
CLOSE_CF_TAG_DELIM,
HTML_TEXT,
CF_VOID_START_TAG_NAME,
CF_SET_START_TAG_NAME,
CF_IF_START_TAG_NAME,
CF_IF_END_TAG_NAME,
CF_ELSEIF_TAG_NAME,
CF_ELSE_TAG_NAME,
CF_RETURN_START_TAG_NAME,
CF_OUTPUT_START_TAG_NAME,
SCRIPT_START_TAG_NAME,
STYLE_START_TAG_NAME,
START_TAG_NAME,
END_TAG_NAME,
ERRONEOUS_END_TAG_NAME,
SELF_CLOSING_TAG_DELIMITER,
IMPLICIT_END_TAG,
START_HASH_EXPRESSION,
SINGLE_HASH,
HASH_EMPTY,
CF_XML_START_TAG_NAME,
CF_XML_END_TAG_NAME,
CF_XML_CONTENT,
CF_QUERY_START_TAG_NAME,
CF_QUERY_END_TAG_NAME,
CF_QUERY_CONTENT,
CF_SCRIPT_START_TAG_NAME,
CF_SCRIPT_END_TAG_NAME,
CF_SCRIPT_CONTENT,
CF_SAVECONTENT_START_TAG_NAME,
CF_SAVECONTENT_END_TAG_NAME,
CF_SAVECONTENT_BODY_CFML,
CF_SAVECONTENT_BODY_HTML,
CF_SAVECONTENT_BODY_SCRIPT,
CF_SAVECONTENT_BODY_CSS,
CF_SAVECONTENT_BODY_XML,
CF_SAVECONTENT_BODY_SQL,
CF_SAVECONTENT_BODY_RAW,
CF_SAVECONTENT_CONTENT,
CF_FUNCTION_START_TAG_NAME,
CF_FUNCTION_END_TAG_NAME,
CF_COMPONENT_START_TAG_NAME,
CF_COMPONENT_END_TAG_NAME,
CF_COMPONENT_CONTENT,
SCANNER_SYMBOL_COUNT
};
typedef struct {
Array(Tag) tags;
Array(Tag) cf_tags;
uint16_t cfoutput_depth;
uint16_t cfcomponent_depth;
uint16_t cffunction_depth;
} Scanner;
typedef enum {
REJECT, NO_NEWLINE, ACCEPT, } WhitespaceResult;
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MAX_CF_END_DELIMITER_SIZE 256
#define VS(vs, sym, count) ((unsigned)(sym) < (count) && (vs)[(sym)])
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
static inline bool cf_isspace(int32_t c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\v' || c == '\f' ||
(c > 127 && iswspace((wint_t)c));
}
static inline bool cf_isalpha(int32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c > 127 && iswalpha((wint_t)c));
}
static inline bool cf_isdigit(int32_t c) { return c >= '0' && c <= '9'; }
static inline bool cf_isxdigit(int32_t c) {
return cf_isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static inline bool cf_isalnum(int32_t c) {
return cf_isdigit(c) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c > 127 && iswalnum((wint_t)c));
}
static inline int32_t cf_toupper(int32_t c) {
if (c >= 'a' && c <= 'z') return c - ('a' - 'A');
if (c > 127) return (int32_t)towupper((wint_t)c);
return c;
}
static inline int32_t cf_tolower(int32_t c) {
if (c >= 'A' && c <= 'Z') return c + ('a' - 'A');
if (c > 127) return (int32_t)towlower((wint_t)c);
return c;
}
static inline bool tag_has_name(TagType type, bool is_cfquery_context) {
return type == CUSTOM || type == CFML || type == CF_VOID || type == CF_SET ||
type == CF_XML || type == CF_SCRIPT || type == CF_SAVECONTENT ||
type == CF_QUERY || type == CF_OUTPUT || type == CF_FUNCTION || type == CF_RETURN ||
type == CF_IF || type == CF_ELSEIF || type == CF_ELSE;
}
static inline bool valid_start_tag_name(const bool *vs, unsigned count) {
return VS(vs, START_TAG_NAME, count) || VS(vs, SCRIPT_START_TAG_NAME, count) ||
VS(vs, CF_START_TAG_NAME, count) ||
VS(vs, CF_SET_START_TAG_NAME, count) || VS(vs, CF_VOID_START_TAG_NAME, count) ||
VS(vs, CF_RETURN_START_TAG_NAME, count) || VS(vs, CF_XML_START_TAG_NAME, count) ||
VS(vs, CF_QUERY_START_TAG_NAME, count) || VS(vs, CF_SCRIPT_START_TAG_NAME, count) ||
VS(vs, CF_SAVECONTENT_START_TAG_NAME, count) || VS(vs, CF_OUTPUT_START_TAG_NAME, count) ||
VS(vs, CF_FUNCTION_START_TAG_NAME, count) ||
VS(vs, CF_COMPONENT_START_TAG_NAME, count) ||
VS(vs, CF_IF_START_TAG_NAME, count) || VS(vs, CF_ELSEIF_TAG_NAME, count) ||
VS(vs, CF_ELSE_TAG_NAME, count);
}
static inline bool valid_end_tag_name(const bool *vs, unsigned count) {
return VS(vs, END_TAG_NAME, count) || VS(vs, CF_END_TAG_NAME, count) ||
VS(vs, CF_XML_END_TAG_NAME, count) || VS(vs, CF_QUERY_END_TAG_NAME, count) ||
VS(vs, CF_SCRIPT_END_TAG_NAME, count) || VS(vs, CF_SAVECONTENT_END_TAG_NAME, count) ||
VS(vs, CF_FUNCTION_END_TAG_NAME, count) ||
VS(vs, CF_IF_END_TAG_NAME, count);
}
static inline bool valid_cf_start_tag_name(const bool *vs, unsigned count) {
return VS(vs, CF_START_TAG_NAME, count) || VS(vs, CF_SET_START_TAG_NAME, count) ||
VS(vs, CF_VOID_START_TAG_NAME, count) || VS(vs, CF_RETURN_START_TAG_NAME, count) ||
VS(vs, CF_XML_START_TAG_NAME, count) || VS(vs, CF_QUERY_START_TAG_NAME, count) ||
VS(vs, CF_SCRIPT_START_TAG_NAME, count) || VS(vs, CF_SAVECONTENT_START_TAG_NAME, count) ||
VS(vs, CF_OUTPUT_START_TAG_NAME, count) || VS(vs, CF_FUNCTION_START_TAG_NAME, count) ||
VS(vs, CF_COMPONENT_START_TAG_NAME, count) ||
VS(vs, CF_IF_START_TAG_NAME, count) || VS(vs, CF_ELSEIF_TAG_NAME, count) ||
VS(vs, CF_ELSE_TAG_NAME, count);
}
static inline bool valid_cf_end_tag_name(const bool *vs, unsigned count) {
return VS(vs, CF_END_TAG_NAME, count) || VS(vs, CF_XML_END_TAG_NAME, count) ||
VS(vs, CF_QUERY_END_TAG_NAME, count) || VS(vs, CF_SCRIPT_END_TAG_NAME, count) ||
VS(vs, CF_SAVECONTENT_END_TAG_NAME, count) || VS(vs, CF_FUNCTION_END_TAG_NAME, count) ||
VS(vs, CF_IF_END_TAG_NAME, count);
}
static inline bool no_content_symbols(const bool *vs, unsigned count) {
return !VS(vs, RAW_TEXT, count) && !VS(vs, CF_XML_CONTENT, count) &&
!VS(vs, CF_QUERY_CONTENT, count) && !VS(vs, CF_SCRIPT_CONTENT, count);
}
static inline bool implicit_cf_end_tag_valid(const bool *vs, unsigned count) {
return VS(vs, IMPLICIT_CF_END_TAG, count) && !VS(vs, CF_XML_END_TAG_NAME, count) &&
!VS(vs, CF_QUERY_END_TAG_NAME, count) && !VS(vs, CF_SCRIPT_END_TAG_NAME, count) &&
!VS(vs, CF_SAVECONTENT_END_TAG_NAME, count) && !VS(vs, CF_FUNCTION_END_TAG_NAME, count) &&
!VS(vs, CF_IF_END_TAG_NAME, count) &&
!VS(vs, CF_ELSEIF_TAG_NAME, count) && !VS(vs, CF_ELSE_TAG_NAME, count);
}
#define TAGS_HEADER_SIZE (2 * sizeof(uint16_t))
static unsigned tag_serialized_size(const Tag *tag, bool is_cfquery_context) {
if (!tag_has_name(tag->type, is_cfquery_context)) return 1;
unsigned len = tag->tag_name.size;
if (len > UINT8_MAX) len = UINT8_MAX;
return 2 + len + sizeof(tag->html_depth);
}
#define SERIALIZE_TAGS(tags_field, buffer, size, reserve, is_cfquery_context) do { \
uint16_t _count = (tags_field).size > UINT16_MAX ? UINT16_MAX : (tags_field).size; \
uint16_t _serialized = 0; \
unsigned _count_offset = (size); \
unsigned _limit = TREE_SITTER_SERIALIZATION_BUFFER_SIZE - (reserve); \
\
\
\
if ((size) + TAGS_HEADER_SIZE > _limit) break; \
(size) += TAGS_HEADER_SIZE; \
for (; _serialized < _count; _serialized++) { \
Tag _tag = (tags_field).contents[_serialized]; \
if (tag_has_name(_tag.type, is_cfquery_context)) { \
unsigned _len = _tag.tag_name.size; \
if (_len > UINT8_MAX) _len = UINT8_MAX; \
if ((size) + 2 + _len + sizeof(_tag.html_depth) > _limit) break; \
(buffer)[(size)++] = (char)_tag.type; \
(buffer)[(size)++] = (char)_len; \
memcpy(&(buffer)[(size)], _tag.tag_name.contents, _len); \
(size) += _len; \
memcpy(&(buffer)[(size)], &_tag.html_depth, sizeof(_tag.html_depth)); \
(size) += sizeof(_tag.html_depth); \
} else { \
if ((size) + 1 > _limit) break; \
(buffer)[(size)++] = (char)_tag.type; \
} \
} \
memcpy(&(buffer)[_count_offset], &_serialized, sizeof(_serialized)); \
memcpy(&(buffer)[_count_offset + sizeof(_serialized)], &_count, sizeof(_count)); \
} while(0)
#define TAG_STACK_HEADROOM 256
static bool tag_stack_would_overflow(const Scanner *scanner, const Tag *incoming,
bool is_cfquery_context) {
const unsigned depths = sizeof(scanner->cfoutput_depth) +
sizeof(scanner->cfcomponent_depth) +
sizeof(scanner->cffunction_depth);
const unsigned budget =
TREE_SITTER_SERIALIZATION_BUFFER_SIZE -
(2 * TAGS_HEADER_SIZE + depths) - TAG_STACK_HEADROOM;
unsigned used = 0;
for (unsigned i = 0; i < scanner->tags.size; i++) {
used += tag_serialized_size(&scanner->tags.contents[i], is_cfquery_context);
}
for (unsigned i = 0; i < scanner->cf_tags.size; i++) {
used += tag_serialized_size(&scanner->cf_tags.contents[i], is_cfquery_context);
}
return used + tag_serialized_size(incoming, is_cfquery_context) > budget;
}
static unsigned serialize(Scanner *scanner, char *buffer, bool is_cfquery_context) {
unsigned size = 0;
const unsigned depths = sizeof(scanner->cfoutput_depth) +
sizeof(scanner->cfcomponent_depth) +
sizeof(scanner->cffunction_depth);
SERIALIZE_TAGS(scanner->tags, buffer, size, TAGS_HEADER_SIZE + depths, is_cfquery_context);
SERIALIZE_TAGS(scanner->cf_tags, buffer, size, depths, is_cfquery_context);
if (size + depths <= TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
memcpy(&buffer[size], &scanner->cfoutput_depth, sizeof(scanner->cfoutput_depth));
size += sizeof(scanner->cfoutput_depth);
memcpy(&buffer[size], &scanner->cfcomponent_depth, sizeof(scanner->cfcomponent_depth));
size += sizeof(scanner->cfcomponent_depth);
memcpy(&buffer[size], &scanner->cffunction_depth, sizeof(scanner->cffunction_depth));
size += sizeof(scanner->cffunction_depth);
}
return size;
}
#define DESERIALIZE_TAGS(tags_field, buffer, size, length, is_cfquery_context) do { \
uint16_t _serialized = 0, _count = 0; \
if ((size) + TAGS_HEADER_SIZE <= (length)) { \
memcpy(&_serialized, &(buffer)[(size)], sizeof(_serialized)); (size) += sizeof(_serialized); \
memcpy(&_count, &(buffer)[(size)], sizeof(_count)); (size) += sizeof(_count); \
} \
for (unsigned _i = _count; _i < (tags_field).size; _i++) tag_free(&(tags_field).contents[_i]); \
if ((tags_field).size > _count) (tags_field).size = _count; \
array_reserve(&(tags_field), _count); \
while ((tags_field).size < _count) array_push(&(tags_field), tag_new()); \
bool _stop = false; \
for (unsigned _i = 0; _i < _count; _i++) { \
Tag *_tag = &(tags_field).contents[_i]; \
bool _filled = false; \
if (!_stop && _i < _serialized && (size) + 1 <= (length)) { \
TagType _type = (TagType)(unsigned char)(buffer)[(size)]; \
if (!tag_has_name(_type, is_cfquery_context)) { \
(size) += 1; \
_tag->type = _type; \
_tag->tag_name.size = 0; \
_tag->html_depth = 0; \
_filled = true; \
} else if ((size) + 2 <= (length)) { \
uint16_t _len = (uint8_t)(buffer)[(size) + 1]; \
if ((size) + 2 + _len + sizeof(_tag->html_depth) <= (length)) { \
(size) += 2; \
_tag->type = _type; \
array_reserve(&_tag->tag_name, _len); \
\
\
if (_len) memcpy(_tag->tag_name.contents, &(buffer)[(size)], _len); \
_tag->tag_name.size = _len; \
(size) += _len; \
memcpy(&_tag->html_depth, &(buffer)[(size)], sizeof(_tag->html_depth)); \
(size) += sizeof(_tag->html_depth); \
_filled = true; \
} \
} \
} \
if (!_filled) { \
_stop = true; \
_tag->type = END_; \
_tag->tag_name.size = 0; \
_tag->html_depth = 0; \
} \
} \
} while(0)
static void deserialize(Scanner *scanner, const char *buffer, unsigned length, bool is_cfquery_context) {
scanner->cfoutput_depth = 0;
scanner->cfcomponent_depth = 0;
scanner->cffunction_depth = 0;
if (length > 0) {
unsigned size = 0;
DESERIALIZE_TAGS(scanner->tags, buffer, size, length, is_cfquery_context);
DESERIALIZE_TAGS(scanner->cf_tags, buffer, size, length, is_cfquery_context);
if (size + sizeof(scanner->cfoutput_depth) <= length) {
memcpy(&scanner->cfoutput_depth, &buffer[size], sizeof(scanner->cfoutput_depth));
size += sizeof(scanner->cfoutput_depth);
}
if (size + sizeof(scanner->cfcomponent_depth) <= length) {
memcpy(&scanner->cfcomponent_depth, &buffer[size], sizeof(scanner->cfcomponent_depth));
size += sizeof(scanner->cfcomponent_depth);
}
if (size + sizeof(scanner->cffunction_depth) <= length) {
memcpy(&scanner->cffunction_depth, &buffer[size], sizeof(scanner->cffunction_depth));
size += sizeof(scanner->cffunction_depth);
}
} else {
for (unsigned i = 0; i < scanner->tags.size; i++) tag_free(&scanner->tags.contents[i]);
array_clear(&scanner->tags);
for (unsigned i = 0; i < scanner->cf_tags.size; i++) tag_free(&scanner->cf_tags.contents[i]);
array_clear(&scanner->cf_tags);
}
}
typedef struct {
String tag_name;
bool is_cf_tag;
} TagNameResult;
static TagNameResult scan_tag_name(TSLexer *lexer, bool is_cfquery_context) {
TagNameResult result;
String tag_name = array_new();
bool is_cf_tag = false;
array_reserve(&tag_name, TAG_NAME_FIELD);
if ( lexer->lookahead == 'c' || lexer->lookahead == 'C' ) {
array_push(&tag_name, cf_toupper(lexer->lookahead));
advance(lexer);
if (lexer->lookahead == 'f' || lexer->lookahead == 'F') {
is_cf_tag = true;
array_delete(&tag_name);
advance(lexer);
}
}
while (( cf_isalnum(lexer->lookahead) || lexer->lookahead == '-' || lexer->lookahead == '_' || lexer->lookahead == ':' )) {
array_push(&tag_name, cf_toupper(lexer->lookahead));
advance(lexer);
}
result.tag_name = tag_name;
result.is_cf_tag = is_cf_tag;
return result;
}
static bool scan_comment(TSLexer *lexer, bool is_cfquery_context) {
if (lexer->lookahead != '-') {
return false;
}
advance(lexer);
if (lexer->lookahead != '-') {
return false;
}
advance(lexer);
if (lexer->lookahead == '[' || lexer->lookahead == '<') {
unsigned close_dashes = 0;
while (lexer->lookahead) {
if (lexer->lookahead == '-') {
close_dashes++;
} else if (lexer->lookahead == '>' && close_dashes >= 2) {
lexer->result_symbol = CFML_COMMENT;
advance(lexer);
lexer->mark_end(lexer);
return true;
} else {
close_dashes = 0;
}
advance(lexer);
}
return false;
}
unsigned dashes = 0;
unsigned direction = -1;
unsigned nesting = 1;
while (lexer->lookahead) {
switch (lexer->lookahead) {
case '-':
++dashes;
if ( direction == 1 && dashes >= 2 ) {
++nesting;
direction = -1;
dashes = 0;
}
break;
case '>':
if (dashes >= 2) {
--nesting;
lexer->result_symbol = CFML_COMMENT;
advance(lexer);
lexer->mark_end(lexer);
if ( nesting == 0 ) {
return true;
}
dashes = 0;
direction = -1;
continue;
}
direction = -1;
dashes = 0;
break;
case '<':
direction = 0;
dashes = 0;
break;
case '!':
if ( direction == 0 ) {
direction = 1;
break;
}
direction = -1;
dashes = 0;
break;
default:
direction = -1;
dashes = 0;
break;
}
advance(lexer);
}
return false;
}
static WhitespaceResult scan_whitespace_and_comments(TSLexer *lexer, bool *scanned_comment, bool consume, bool is_cfquery_context) {
bool saw_block_newline = false;
for (;;) {
while (cf_isspace(lexer->lookahead)) {
skip(lexer);
}
if (lexer->lookahead == '/') {
skip(lexer);
if (lexer->lookahead == '/') {
skip(lexer);
while (lexer->lookahead != 0 && lexer->lookahead != '\n' && lexer->lookahead != 0x2028 && lexer->lookahead != 0x2029) {
skip(lexer);
}
*scanned_comment = true;
} else if (lexer->lookahead == '*') {
skip(lexer);
while (lexer->lookahead != 0) {
if (lexer->lookahead == '*') {
skip(lexer);
if (lexer->lookahead == '/') {
skip(lexer);
*scanned_comment = true;
if (lexer->lookahead != '/' && !consume) {
return saw_block_newline ? ACCEPT : NO_NEWLINE;
}
break;
}
} else if (lexer->lookahead == '\n' || lexer->lookahead == 0x2028 || lexer->lookahead == 0x2029) {
saw_block_newline = true;
skip(lexer);
} else {
skip(lexer);
}
}
} else {
return REJECT;
}
} else {
return ACCEPT;
}
}
}
static void skip_cfml_comment_body(TSLexer *lexer) {
unsigned depth = 1;
while (lexer->lookahead) {
if (lexer->lookahead == '<') {
advance(lexer);
if (lexer->lookahead != '!') continue;
advance(lexer);
unsigned dashes = 0;
while (lexer->lookahead == '-') {
dashes++;
advance(lexer);
}
if (dashes >= 3) depth++;
continue;
}
if (lexer->lookahead == '-') {
unsigned dashes = 0;
while (lexer->lookahead == '-') {
dashes++;
advance(lexer);
}
if (dashes >= 3 && lexer->lookahead == '>') {
advance(lexer);
if (--depth == 0) return;
}
continue;
}
advance(lexer);
}
}
static bool scan_html_text(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context,
const bool *valid_symbols, unsigned count) {
bool in_script_style = false;
if (scanner->tags.size > 0) {
TagType type = array_back(&scanner->tags)->type;
if (type == SCRIPT || type == STYLE) {
in_script_style = true;
}
}
bool saw_text = false;
bool at_newline = false;
bool saw_any = false;
if (in_script_style) {
lexer->mark_end(lexer);
while (lexer->lookahead != 0 && lexer->lookahead != '#') {
if (lexer->lookahead == '<') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'C') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'F') {
break;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
} else if (lexer->lookahead == '/') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'C') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'F') {
break;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
} else if (cf_toupper(lexer->lookahead) == 'S') {
break;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
} else if (lexer->lookahead == '!') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
if (lexer->lookahead == '-') {
skip_cfml_comment_body(lexer);
}
}
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
}
saw_text = true;
saw_any = true;
advance(lexer);
lexer->mark_end(lexer);
}
lexer->result_symbol = HTML_TEXT;
return saw_text || (saw_any && lexer->lookahead == '#');
}
bool tag_delimiter_expected =
VS(valid_symbols, CLOSE_TAG_DELIM, count) ||
VS(valid_symbols, CLOSE_CF_TAG_DELIM, count) ||
VS(valid_symbols, SELF_CLOSING_TAG_DELIMITER, count) ||
VS(valid_symbols, CF_SELF_CLOSING_TAG_DELIMITER, count) ||
VS(valid_symbols, CF_SELF_CLOSING_VOID_TAG_DELIMITER, count);
while (lexer->lookahead != 0 && lexer->lookahead != '{' &&
lexer->lookahead != '}' && lexer->lookahead != '#') {
if (lexer->lookahead == '<') {
if (!saw_text) {
break;
}
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == 0 || cf_isalpha(lexer->lookahead) || lexer->lookahead == '/' ||
lexer->lookahead == '!' || lexer->lookahead == '?' || lexer->lookahead == '#') {
break;
}
saw_text = true;
saw_any = true;
lexer->mark_end(lexer);
continue;
}
if (lexer->lookahead == '>') {
if (tag_delimiter_expected) {
break;
}
advance(lexer);
saw_text = true;
saw_any = true;
lexer->mark_end(lexer);
continue;
}
if (lexer->lookahead == '&') {
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == '#') {
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
advance(lexer);
if (lexer->lookahead == 'x' || lexer->lookahead == 'X' || cf_isdigit(lexer->lookahead)) {
if (lexer->lookahead == 'x' || lexer->lookahead == 'X') {
advance(lexer);
while (cf_isxdigit(lexer->lookahead)) advance(lexer);
} else {
while (cf_isdigit(lexer->lookahead)) advance(lexer);
}
if (lexer->lookahead == ';') advance(lexer);
lexer->mark_end(lexer);
continue;
}
break;
}
if (cf_isalpha(lexer->lookahead)) {
unsigned count = 0;
while (cf_isalpha(lexer->lookahead) && count < 31) {
advance(lexer);
count++;
}
if (lexer->lookahead == ';' && count > 0) {
break;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
}
bool is_wspace = cf_isspace(lexer->lookahead);
if (lexer->lookahead == '\n') {
at_newline = true;
} else {
at_newline &= is_wspace;
if (!at_newline) {
saw_text = true;
}
}
saw_any = true;
advance(lexer);
lexer->mark_end(lexer);
}
lexer->result_symbol = HTML_TEXT;
return saw_text || (saw_any && lexer->lookahead == '#');
}
static bool scan_script_comment(TSLexer *lexer, bool is_cfquery_context) {
for (;;) {
if (lexer->lookahead == '/') {
skip(lexer);
while (lexer->lookahead != 0 && lexer->lookahead != '\n' && lexer->lookahead != 0x2028 &&
lexer->lookahead != 0x2029) {
skip(lexer);
}
} else if (lexer->lookahead == '*') {
skip(lexer);
while (lexer->lookahead != 0) {
if (lexer->lookahead == '*') {
skip(lexer);
if (lexer->lookahead == '/') {
skip(lexer);
break;
}
} else {
skip(lexer);
}
}
} else {
return false;
}
}
}
static bool scan_cfquery_content(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
if (scanner->cf_tags.size == 0) {
return false;
}
Tag *cf_tag = array_back(&scanner->cf_tags);
if (cf_tag->type != CF_QUERY) {
return false;
}
lexer->mark_end(lexer);
size_t tag_len = cf_tag->tag_name.size;
if (tag_len > MAX_CF_END_DELIMITER_SIZE - 5) return false;
char end_delimiter[MAX_CF_END_DELIMITER_SIZE];
memcpy(end_delimiter, "</CF", 4);
memcpy(&end_delimiter[4], cf_tag->tag_name.contents, tag_len);
end_delimiter[4 + tag_len] = '\0';
size_t delimiter_index = 0;
size_t end_delim_len = 4 + tag_len;
while (lexer->lookahead) {
if (cf_toupper(lexer->lookahead) == end_delimiter[delimiter_index]) {
delimiter_index++;
if (delimiter_index == end_delim_len) {
break;
}
advance(lexer);
} else {
delimiter_index = 0;
advance(lexer);
lexer->mark_end(lexer);
}
}
lexer->result_symbol = CF_QUERY_CONTENT;
return true;
}
static bool scan_cfxml_content(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
if (scanner->cf_tags.size == 0) {
return false;
}
Tag *cf_tag = array_back(&scanner->cf_tags);
if (cf_tag->type != CF_XML) {
return false;
}
lexer->mark_end(lexer);
size_t tag_len = cf_tag->tag_name.size;
if (tag_len > MAX_CF_END_DELIMITER_SIZE - 5) return false;
char end_delimiter[MAX_CF_END_DELIMITER_SIZE];
memcpy(end_delimiter, "</CF", 4);
memcpy(&end_delimiter[4], cf_tag->tag_name.contents, tag_len);
end_delimiter[4 + tag_len] = '\0';
size_t delimiter_index = 0;
size_t end_delim_len = 4 + tag_len;
while (lexer->lookahead) {
if (cf_toupper(lexer->lookahead) == end_delimiter[delimiter_index]) {
delimiter_index++;
if (delimiter_index == end_delim_len) {
break;
}
advance(lexer);
} else {
delimiter_index = 0;
advance(lexer);
lexer->mark_end(lexer);
}
}
lexer->result_symbol = CF_XML_CONTENT;
return true;
}
static bool scan_cfscript_content(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
if (scanner->cf_tags.size == 0) {
return false;
}
Tag *cf_tag = array_back(&scanner->cf_tags);
if (cf_tag->type != CF_SCRIPT) {
return false;
}
lexer->mark_end(lexer);
size_t tag_len = cf_tag->tag_name.size;
if (tag_len > MAX_CF_END_DELIMITER_SIZE - 5) return false;
char end_delimiter[MAX_CF_END_DELIMITER_SIZE];
memcpy(end_delimiter, "</CF", 4);
memcpy(&end_delimiter[4], cf_tag->tag_name.contents, tag_len);
end_delimiter[4 + tag_len] = '\0';
size_t delimiter_index = 0;
size_t end_delim_len = 4 + tag_len;
while (lexer->lookahead) {
if (cf_toupper(lexer->lookahead) == end_delimiter[delimiter_index]) {
delimiter_index++;
if (delimiter_index == end_delim_len) {
break;
}
advance(lexer);
} else {
delimiter_index = 0;
advance(lexer);
lexer->mark_end(lexer);
}
}
lexer->result_symbol = CF_SCRIPT_CONTENT;
return true;
}
static bool scan_cfsavecontent_body_type(Scanner *scanner, TSLexer *lexer, const bool *valid_symbols, unsigned count, bool is_cfquery_context) {
if (scanner->cf_tags.size == 0) return false;
Tag *cf_tag = array_back(&scanner->cf_tags);
if (cf_tag->type != CF_SAVECONTENT) return false;
unsigned result = CF_SAVECONTENT_BODY_CFML;
lexer->mark_end(lexer);
while (cf_isspace(lexer->lookahead)) advance(lexer);
if (lexer->lookahead == '<') {
advance(lexer);
if (lexer->lookahead == '!') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
while (cf_isspace(lexer->lookahead)) advance(lexer);
const char *directive = "@content";
size_t di = 0;
bool matched = true;
while (di < 8) {
if (lexer->lookahead != directive[di]) { matched = false; break; }
advance(lexer);
di++;
}
if (matched) {
while (cf_isspace(lexer->lookahead)) advance(lexer);
char type_buf[16];
int len = 0;
while (cf_isalpha(lexer->lookahead) && len < 15) {
type_buf[len++] = cf_tolower(lexer->lookahead);
advance(lexer);
}
type_buf[len] = '\0';
if (strcmp(type_buf, "script") == 0) result = CF_SAVECONTENT_BODY_SCRIPT;
else if (strcmp(type_buf, "css") == 0) result = CF_SAVECONTENT_BODY_CSS;
else if (strcmp(type_buf, "xml") == 0) result = CF_SAVECONTENT_BODY_XML;
else if (strcmp(type_buf, "sql") == 0) result = CF_SAVECONTENT_BODY_SQL;
else if (strcmp(type_buf, "raw") == 0) result = CF_SAVECONTENT_BODY_RAW;
else if (strcmp(type_buf, "html") == 0) result = CF_SAVECONTENT_BODY_HTML;
}
}
}
}
}
}
if (!VS(valid_symbols, result, count)) return false;
lexer->result_symbol = result;
return true;
}
static bool scan_cfsavecontent_content(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
if (scanner->cf_tags.size == 0) return false;
Tag *cf_tag = array_back(&scanner->cf_tags);
if (cf_tag->type != CF_SAVECONTENT) return false;
lexer->mark_end(lexer);
size_t tag_len = cf_tag->tag_name.size;
if (tag_len > MAX_CF_END_DELIMITER_SIZE - 5) return false;
char end_delimiter[MAX_CF_END_DELIMITER_SIZE];
memcpy(end_delimiter, "</CF", 4);
memcpy(&end_delimiter[4], cf_tag->tag_name.contents, tag_len);
end_delimiter[4 + tag_len] = '\0';
size_t end_delim_len = 4 + tag_len;
bool has_content = false;
while (lexer->lookahead) {
if (lexer->lookahead == '<') {
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == '/') {
advance(lexer);
size_t i = 2;
bool matched = true;
while (i < end_delim_len) {
if (cf_toupper(lexer->lookahead) != end_delimiter[i]) { matched = false; break; }
i++;
if (i < end_delim_len) advance(lexer);
}
if (matched) {
lexer->result_symbol = CF_SAVECONTENT_CONTENT;
return has_content;
}
}
has_content = true;
lexer->mark_end(lexer);
} else {
has_content = true;
advance(lexer);
lexer->mark_end(lexer);
}
}
lexer->result_symbol = CF_SAVECONTENT_CONTENT;
return has_content;
}
static bool scan_raw_text(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
if (scanner->tags.size == 0) {
return false;
}
lexer->mark_end(lexer);
const char *end_delimiter = array_back(&scanner->tags)->type == SCRIPT ? "</SCRIPT" : "</STYLE";
const unsigned end_delimiter_len = array_back(&scanner->tags)->type == SCRIPT ? 8 : 7;
bool stop_at_cfml = !is_cfquery_context &&
(array_back(&scanner->tags)->type == SCRIPT || array_back(&scanner->tags)->type == STYLE);
bool has_content = false;
unsigned delimiter_index = 0;
while (lexer->lookahead) {
if (stop_at_cfml && delimiter_index == 0) {
if (lexer->lookahead == '#' && scanner->cfoutput_depth > 0) {
break;
}
if (lexer->lookahead == '<') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'C') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'F') {
break;
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else if (lexer->lookahead == '/') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'C') {
advance(lexer);
if (cf_toupper(lexer->lookahead) == 'F') {
break;
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else if (cf_toupper(lexer->lookahead) == end_delimiter[2]) {
delimiter_index = 3;
advance(lexer);
continue;
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else if (lexer->lookahead == '!') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
if (lexer->lookahead == '-') {
skip_cfml_comment_body(lexer);
lexer->mark_end(lexer);
has_content = true;
continue;
}
}
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else {
lexer->mark_end(lexer);
has_content = true;
continue;
}
}
}
if (cf_toupper(lexer->lookahead) == end_delimiter[delimiter_index]) {
delimiter_index++;
if (delimiter_index == end_delimiter_len) {
break;
}
advance(lexer);
} else {
delimiter_index = 0;
advance(lexer);
lexer->mark_end(lexer);
has_content = true;
}
}
if (!has_content) {
return false;
}
lexer->result_symbol = RAW_TEXT;
return true;
}
static void pop_tag(Scanner *scanner, bool is_cf_context) {
if ( is_cf_context ) {
Tag popped_tag = array_pop(&scanner->cf_tags);
tag_free(&popped_tag);
} else {
Tag popped_tag = array_pop(&scanner->tags);
tag_free(&popped_tag);
}
}
static bool scan_implicit_end_tag(Scanner *scanner, TSLexer *lexer, bool is_cf_context, bool is_cfquery_context, bool from_tag_open) {
Tag *parent = is_cf_context
? (scanner->cf_tags.size == 0 ? NULL : array_back(&scanner->cf_tags))
: (scanner->tags.size == 0 ? NULL : array_back(&scanner->tags));
bool is_closing_tag = false;
if (lexer->lookahead == '/') {
is_closing_tag = true;
advance(lexer);
} else {
if (!is_cf_context && parent && tag_is_void(parent)) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
return true;
}
if (is_cf_context && parent && cf_tag_is_void(parent)) {
pop_tag(scanner, true);
lexer->result_symbol = IMPLICIT_CF_END_TAG;
return true;
}
}
TagNameResult result = scan_tag_name(lexer, is_cfquery_context);
if (result.tag_name.size == 0 && !lexer->eof(lexer)) {
array_delete(&result.tag_name);
return false;
}
if (result.is_cf_tag && !is_closing_tag &&
((result.tag_name.size == 4 && memcmp(result.tag_name.contents, "ELSE", 4) == 0) ||
(result.tag_name.size == 6 && memcmp(result.tag_name.contents, "ELSEIF", 6) == 0))) {
array_delete(&result.tag_name);
if (is_cf_context && parent &&
parent->type != CF_IF && parent->type != CF_ELSEIF && parent->type != CF_ELSE) {
pop_tag(scanner, true);
lexer->result_symbol = IMPLICIT_CF_END_TAG;
return true;
}
if (!is_cf_context && scanner->tags.size > 0) {
for (unsigned i = scanner->cf_tags.size; i > 0; i--) {
Tag *ct = &scanner->cf_tags.contents[i - 1];
if (ct->type == CF_IF || ct->type == CF_ELSEIF || ct->type == CF_ELSE) {
if (scanner->tags.size > ct->html_depth) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
return true;
}
break;
}
}
}
return false;
}
if (result.is_cf_tag && !is_cf_context && is_closing_tag) {
Tag cf_next = cf_tag_for_name(result.tag_name);
unsigned cf_html_depth = 0;
bool found = false;
for (unsigned i = scanner->cf_tags.size; i > 0; i--) {
if (tag_eq(&scanner->cf_tags.contents[i - 1], &cf_next)) {
cf_html_depth = scanner->cf_tags.contents[i - 1].html_depth;
found = true;
break;
}
}
tag_free(&cf_next);
if (found && scanner->tags.size > cf_html_depth) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
return true;
}
return false;
}
if (result.is_cf_tag && !is_cf_context) {
array_delete(&result.tag_name);
return false;
}
Tag next_tag = is_cf_context ? cf_tag_for_name(result.tag_name) : tag_for_name(result.tag_name);
if (is_closing_tag) {
if (is_cf_context ? (scanner->cf_tags.size > 0 && tag_eq(array_back(&scanner->cf_tags), &next_tag))
: (scanner->tags.size > 0 && tag_eq(array_back(&scanner->tags), &next_tag))) {
if (is_cf_context && scanner->tags.size > array_back(&scanner->cf_tags)->html_depth) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
tag_free(&next_tag);
return true;
}
tag_free(&next_tag);
return false;
}
if (is_cf_context) {
for (unsigned i = scanner->cf_tags.size; i > 0; i--) {
if (tag_eq(&scanner->cf_tags.contents[i - 1], &next_tag)) {
pop_tag(scanner, true);
lexer->result_symbol = IMPLICIT_CF_END_TAG;
tag_free(&next_tag);
return true;
}
}
} else {
for (unsigned i = scanner->tags.size; i > 0; i--) {
if (tag_eq(&scanner->tags.contents[i - 1], &next_tag)) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
tag_free(&next_tag);
return true;
}
}
}
} else {
if (from_tag_open && is_cf_context && !result.is_cf_tag && parent && tag_eq(parent, &next_tag)) {
pop_tag(scanner, true);
lexer->result_symbol = IMPLICIT_CF_END_TAG;
tag_free(&next_tag);
return true;
}
if (!is_cf_context && parent && tag_eq(parent, &next_tag) && tag_implicitly_closes_self(parent)) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
tag_free(&next_tag);
return true;
}
if (
parent &&
(
(lexer->eof(lexer))
|| (is_cf_context && lexer->eof(lexer))
)
) {
pop_tag(scanner, is_cf_context);
lexer->result_symbol = is_cf_context ? IMPLICIT_CF_END_TAG : IMPLICIT_END_TAG;
tag_free(&next_tag);
return true;
}
}
tag_free(&next_tag);
return false;
}
static bool scan_start_tag_name(Scanner *scanner, TSLexer *lexer, bool is_cf_context, bool is_cfquery_context) {
if (lexer->lookahead == '#') {
Tag tag = tag_new();
tag.type = DYNAMIC;
array_push(&scanner->tags, tag);
lexer->result_symbol = START_TAG_NAME;
return true;
}
TagNameResult result = scan_tag_name(lexer, is_cfquery_context);
if (result.tag_name.size == 0) {
array_delete(&result.tag_name);
return false;
}
if ( result.is_cf_tag && !is_cf_context ) {
array_delete(&result.tag_name);
return false;
}
Tag tag = is_cf_context ? cf_tag_for_name(result.tag_name) : tag_for_name(result.tag_name);
switch (tag.type) {
case SCRIPT:
lexer->result_symbol = SCRIPT_START_TAG_NAME;
break;
case STYLE:
lexer->result_symbol = STYLE_START_TAG_NAME;
break;
case CF_VOID:
if (is_cf_context && tag.tag_name.size == 9 &&
memcmp(tag.tag_name.contents, "COMPONENT", 9) == 0) {
scanner->cfcomponent_depth++;
lexer->result_symbol = CF_COMPONENT_START_TAG_NAME;
} else {
lexer->result_symbol = CF_VOID_START_TAG_NAME;
}
tag_free(&tag);
return true;
case CF_SET:
lexer->result_symbol = CF_SET_START_TAG_NAME;
return true;
case CF_RETURN:
lexer->result_symbol = CF_RETURN_START_TAG_NAME;
return true;
case CF_IF:
lexer->result_symbol = CF_IF_START_TAG_NAME;
break;
case CF_ELSEIF:
lexer->result_symbol = CF_ELSEIF_TAG_NAME;
return true;
case CF_ELSE:
lexer->result_symbol = CF_ELSE_TAG_NAME;
return true;
case CF_XML:
lexer->result_symbol = CF_XML_START_TAG_NAME;
break;
case CF_QUERY:
lexer->result_symbol = CF_QUERY_START_TAG_NAME;
break;
case CF_SCRIPT:
lexer->result_symbol = CF_SCRIPT_START_TAG_NAME;
break;
case CF_SAVECONTENT:
lexer->result_symbol = CF_SAVECONTENT_START_TAG_NAME;
break;
case CF_OUTPUT:
lexer->result_symbol = CF_OUTPUT_START_TAG_NAME;
if (is_cf_context) {
scanner->cfoutput_depth++;
}
break;
case CF_FUNCTION:
lexer->result_symbol = CF_FUNCTION_START_TAG_NAME;
if (is_cf_context) {
scanner->cffunction_depth++;
}
break;
default:
lexer->result_symbol = is_cf_context ? CF_START_TAG_NAME : START_TAG_NAME;
break;
}
if (is_cf_context && tag.type == CFML &&
tag_stack_would_overflow(scanner, &tag, is_cfquery_context)) {
lexer->result_symbol = CF_VOID_START_TAG_NAME;
tag_free(&tag);
return true;
}
if ( is_cf_context ) {
tag.html_depth = scanner->tags.size;
array_push(&scanner->cf_tags, tag);
} else {
array_push(&scanner->tags, tag);
}
return true;
}
static void set_end_tag_symbol(Scanner *scanner, TSLexer *lexer, Tag *tag, bool is_cf_context, bool is_cfquery_context) {
if (is_cf_context && tag->type == CF_OUTPUT) {
if (scanner->cfoutput_depth > 0) scanner->cfoutput_depth--;
lexer->result_symbol = CF_END_TAG_NAME;
} else if (is_cf_context && tag->type == CF_FUNCTION) {
if (scanner->cffunction_depth > 0) scanner->cffunction_depth--;
lexer->result_symbol = CF_FUNCTION_END_TAG_NAME;
} else if (is_cf_context && tag->type == CF_XML) {
lexer->result_symbol = CF_XML_END_TAG_NAME;
} else if (is_cf_context && tag->type == CF_QUERY) {
lexer->result_symbol = CF_QUERY_END_TAG_NAME;
} else if (is_cf_context && tag->type == CF_SCRIPT) {
lexer->result_symbol = CF_SCRIPT_END_TAG_NAME;
} else if (is_cf_context && tag->type == CF_SAVECONTENT) {
lexer->result_symbol = CF_SAVECONTENT_END_TAG_NAME;
} else if (is_cf_context && tag->type == CF_IF) {
lexer->result_symbol = CF_IF_END_TAG_NAME;
} else {
lexer->result_symbol = is_cf_context ? CF_END_TAG_NAME : END_TAG_NAME;
}
}
static bool scan_end_tag_name(Scanner *scanner, TSLexer *lexer, bool is_cf_context, bool is_cfquery_context) {
if (lexer->lookahead == '#') {
if (scanner->tags.size > 0 && array_back(&scanner->tags)->type == DYNAMIC) {
pop_tag(scanner, false);
}
lexer->result_symbol = END_TAG_NAME;
return true;
}
TagNameResult result = scan_tag_name(lexer, is_cfquery_context);
if (result.tag_name.size == 0) {
array_delete(&result.tag_name);
return false;
}
if ( result.is_cf_tag && !is_cf_context ) {
array_delete(&result.tag_name);
return false;
}
Tag tag = is_cf_context ? cf_tag_for_name(result.tag_name) : tag_for_name(result.tag_name);
if (is_cf_context && tag.type == CF_VOID &&
tag.tag_name.size == 9 && memcmp(tag.tag_name.contents, "COMPONENT", 9) == 0) {
if (scanner->cfcomponent_depth > 0) scanner->cfcomponent_depth--;
lexer->result_symbol = CF_COMPONENT_END_TAG_NAME;
tag_free(&tag);
return true;
}
unsigned html_floor = 0;
if (!is_cf_context && scanner->cf_tags.size > 0) {
html_floor = array_back(&scanner->cf_tags)->html_depth;
}
Tag *tag_back = (is_cf_context) ? ( scanner->cf_tags.size > 0 ? array_back(&scanner->cf_tags) : NULL )
: ( scanner->tags.size > html_floor) ? array_back(&scanner->tags) : NULL;
if ( tag_back && tag_eq(tag_back, &tag) ) {
pop_tag(scanner, is_cf_context);
set_end_tag_symbol(scanner, lexer, &tag, is_cf_context, is_cfquery_context);
} else {
bool found = false;
if (is_cf_context) {
for (unsigned i = scanner->cf_tags.size; i > 0; i--) {
if (tag_eq(&scanner->cf_tags.contents[i - 1], &tag)) {
found = true;
break;
}
}
} else {
for (unsigned i = scanner->tags.size; i > html_floor; i--) {
if (tag_eq(&scanner->tags.contents[i - 1], &tag)) {
found = true;
break;
}
}
}
if (found) {
pop_tag(scanner, is_cf_context);
set_end_tag_symbol(scanner, lexer, &tag, is_cf_context, is_cfquery_context);
} else {
lexer->result_symbol = is_cf_context ? ERRONEOUS_CF_END_TAG_NAME : ERRONEOUS_END_TAG_NAME;
}
}
tag_free(&tag);
return true;
}
static bool scan_cf_self_closing_tag_delimiter(Scanner *scanner, TSLexer *lexer, bool is_void, bool is_cfquery_context) {
if (lexer->lookahead == '>') {
advance(lexer);
if (is_void) {
lexer->result_symbol = CF_SELF_CLOSING_VOID_TAG_DELIMITER;
} else {
lexer->result_symbol = CF_SELF_CLOSING_TAG_DELIMITER;
if (scanner->cf_tags.size > 0) {
pop_tag(scanner, true);
}
}
return true;
}
return false;
}
static bool scan_self_closing_tag_delimiter(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
if (lexer->lookahead == '>') {
advance(lexer);
if (scanner->tags.size > 0) {
pop_tag(scanner, false);
}
lexer->result_symbol = SELF_CLOSING_TAG_DELIMITER;
return true;
}
return false;
}
static bool scan_cfml_word_operator(TSLexer *lexer) {
char buf[11] = {0};
int len = 0;
for (; len < 10 && cf_isalpha(lexer->lookahead); len++) {
buf[len] = cf_tolower(lexer->lookahead);
skip(lexer);
}
bool at_end = !cf_isalnum(lexer->lookahead);
if (!at_end) return false;
return (len == 2 && (
(buf[0] == 'o' && buf[1] == 'r') ||
(buf[0] == 'e' && buf[1] == 'q') ||
(buf[0] == 'g' && buf[1] == 't') ||
(buf[0] == 'g' && buf[1] == 'e') ||
(buf[0] == 'l' && buf[1] == 't') ||
(buf[0] == 'l' && buf[1] == 'e') ||
(buf[0] == 'i' && buf[1] == 'n')
)) || (len == 3 && (
(buf[0] == 'a' && buf[1] == 'n' && buf[2] == 'd') ||
(buf[0] == 'n' && buf[1] == 'e' && buf[2] == 'q') ||
(buf[0] == 'n' && buf[1] == 'o' && buf[2] == 't') ||
(buf[0] == 'g' && buf[1] == 't' && buf[2] == 'e') ||
(buf[0] == 'l' && buf[1] == 't' && buf[2] == 'e') ||
(buf[0] == 'm' && buf[1] == 'o' && buf[2] == 'd')
)) || (len == 10 &&
buf[0] == 'i' && buf[1] == 'n' && buf[2] == 's' && buf[3] == 't' &&
buf[4] == 'a' && buf[5] == 'n' && buf[6] == 'c' && buf[7] == 'e' &&
buf[8] == 'o' && buf[9] == 'f'
);
}
static bool scan_automatic_semicolon(TSLexer *lexer, bool comment_condition, bool *scanned_comment, bool is_cfquery_context) {
lexer->result_symbol = AUTOMATIC_SEMICOLON;
lexer->mark_end(lexer);
for (;;) {
if (lexer->lookahead == 0) {
return true;
}
if (lexer->lookahead == '/') {
WhitespaceResult result = scan_whitespace_and_comments(lexer, scanned_comment, false, is_cfquery_context);
if (result == false) {
return false;
}
if (result == true && comment_condition && lexer->lookahead != ',' && lexer->lookahead != '=') {
return true;
}
}
if (lexer->lookahead == '}') {
return true;
}
if (lexer->is_at_included_range_start(lexer)) {
return true;
}
if (lexer->lookahead == '\n' || lexer->lookahead == 0x2028 || lexer->lookahead == 0x2029) {
break;
}
if (!cf_isspace(lexer->lookahead)) {
return false;
}
skip(lexer);
}
skip(lexer);
if (scan_whitespace_and_comments(lexer, scanned_comment, true, is_cfquery_context) == REJECT) {
return false;
}
switch (lexer->lookahead) {
case '`':
case ',':
case ':':
case ';':
case '*':
case '%':
case '>':
case '<':
case '=':
case '[':
case '(':
case '?':
case '^':
case '|':
case '&':
case '/':
return false;
case '.':
skip(lexer);
return cf_isdigit(lexer->lookahead);
case '+':
skip(lexer);
return lexer->lookahead == '+';
case '-':
skip(lexer);
return lexer->lookahead == '-';
case '!':
skip(lexer);
return lexer->lookahead != '=';
case 'i':
case 'a': case 'A':
case 'o': case 'O':
case 'e': case 'E':
case 'n': case 'N':
case 'g': case 'G':
case 'l': case 'L':
case 'm': case 'M':
return !scan_cfml_word_operator(lexer);
default:
break;
}
return true;
}
static bool scan_ternary_qmark(TSLexer *lexer, bool is_cfquery_context) {
for (;;) {
if (!cf_isspace(lexer->lookahead)) {
break;
}
skip(lexer);
}
if (lexer->lookahead == '?') {
advance(lexer);
if (lexer->lookahead == ':') {
advance(lexer);
lexer->mark_end(lexer);
lexer->result_symbol = ELVIS_OPERATOR;
return true;
} else if (lexer->lookahead == '?') {
return false;
}
lexer->mark_end(lexer);
lexer->result_symbol = TERNARY_QMARK;
if (lexer->lookahead == '.') {
advance(lexer);
if (cf_isdigit(lexer->lookahead)) {
return true;
}
return false;
}
return true;
}
return false;
}
static bool scan_closetag_delim(Scanner *scanner, TSLexer *lexer, bool is_cf_context, bool is_cfquery_context) {
if ( lexer->lookahead == '>' ) {
advance(lexer);
lexer->mark_end(lexer);
lexer->result_symbol = is_cf_context ? CLOSE_CF_TAG_DELIM : CLOSE_TAG_DELIM;
return true;
} else {
return false;
}
}
static bool scanner_in_hash_eval_context(Scanner *scanner, bool is_cfquery_context) {
if (scanner->cfoutput_depth > 0 || scanner->cfcomponent_depth > 0 || scanner->cffunction_depth > 0) {
return true;
}
return false;
}
static bool scan_cf_component_content(TSLexer *lexer, bool is_cfquery_context) {
for (;;) {
while (cf_isspace(lexer->lookahead)) advance(lexer);
if (lexer->lookahead == '/') {
advance(lexer);
if (lexer->lookahead == '/') {
advance(lexer);
while (lexer->lookahead != 0 && lexer->lookahead != '\n') advance(lexer);
} else if (lexer->lookahead == '*') {
advance(lexer);
while (lexer->lookahead != 0) {
if (lexer->lookahead == '*') { advance(lexer); if (lexer->lookahead == '/') { advance(lexer); break; } }
else advance(lexer);
}
} else {
return false;
}
} else {
break;
}
}
char word[16];
int len = 0;
while (cf_isalpha(lexer->lookahead) && len < 15) {
word[len++] = cf_tolower(lexer->lookahead);
advance(lexer);
}
word[len] = '\0';
if (cf_isalnum(lexer->lookahead) || lexer->lookahead == '_') return false;
while (strcmp(word, "abstract") == 0 || strcmp(word, "static") == 0
|| strcmp(word, "final") == 0) {
while (cf_isspace(lexer->lookahead)) advance(lexer);
len = 0;
while (cf_isalpha(lexer->lookahead) && len < 15) {
word[len++] = cf_tolower(lexer->lookahead);
advance(lexer);
}
word[len] = '\0';
if (cf_isalnum(lexer->lookahead) || lexer->lookahead == '_') return false;
if (len == 0) return false;
}
if (strcmp(word, "component") != 0 && strcmp(word, "property") != 0 &&
strcmp(word, "interface") != 0 && strcmp(word, "import") != 0) {
return false;
}
while (lexer->lookahead != 0) advance(lexer);
lexer->mark_end(lexer);
lexer->result_symbol = CF_COMPONENT_CONTENT;
return true;
}
static bool external_scanner_scan(Scanner *scanner, TSLexer *lexer, const bool *valid_symbols, unsigned count, bool is_cfquery_context) {
if (!VS(valid_symbols, HTML_TEXT, count) && !VS(valid_symbols, RAW_TEXT, count)) {
while (cf_isspace(lexer->lookahead)) {
skip(lexer);
}
}
if ((VS(valid_symbols, START_HASH_EXPRESSION, count) || VS(valid_symbols, SINGLE_HASH, count) || VS(valid_symbols, HASH_EMPTY, count))
&& !VS(valid_symbols, AUTOMATIC_SEMICOLON, count) && lexer->lookahead == '#') {
advance(lexer);
if (lexer->lookahead == '#') {
advance(lexer);
lexer->mark_end(lexer);
lexer->result_symbol = HASH_EMPTY;
} else if (scanner_in_hash_eval_context(scanner, is_cfquery_context)) {
lexer->result_symbol = START_HASH_EXPRESSION;
} else {
lexer->mark_end(lexer);
lexer->result_symbol = SINGLE_HASH;
}
return true;
}
if (VS(valid_symbols, CF_COMPONENT_CONTENT, count)
&& scanner->tags.size == 0 && scanner->cf_tags.size == 0
&& scan_cf_component_content(lexer, is_cfquery_context)) {
return true;
}
if (VS(valid_symbols, RAW_TEXT, count) && !VS(valid_symbols, START_TAG_NAME, count) && !VS(valid_symbols, END_TAG_NAME, count)) {
if (scan_raw_text(scanner, lexer, is_cfquery_context)) {
return true;
}
}
if (VS(valid_symbols, CF_XML_CONTENT, count)) {
return scan_cfxml_content(scanner, lexer, is_cfquery_context);
}
if (VS(valid_symbols, CF_QUERY_CONTENT, count)) {
return scan_cfquery_content(scanner, lexer, is_cfquery_context);
}
if (VS(valid_symbols, CF_SCRIPT_CONTENT, count)) {
return scan_cfscript_content(scanner, lexer, is_cfquery_context);
}
if (VS(valid_symbols, CF_SAVECONTENT_BODY_CFML, count) || VS(valid_symbols, CF_SAVECONTENT_BODY_HTML, count) ||
VS(valid_symbols, CF_SAVECONTENT_BODY_SCRIPT, count) ||
VS(valid_symbols, CF_SAVECONTENT_BODY_CSS, count) || VS(valid_symbols, CF_SAVECONTENT_BODY_XML, count) ||
VS(valid_symbols, CF_SAVECONTENT_BODY_SQL, count) || VS(valid_symbols, CF_SAVECONTENT_BODY_RAW, count)) {
if (scan_cfsavecontent_body_type(scanner, lexer, valid_symbols, count, is_cfquery_context)) {
return true;
}
}
if (VS(valid_symbols, CF_SAVECONTENT_CONTENT, count)) {
return scan_cfsavecontent_content(scanner, lexer, is_cfquery_context);
}
if (VS(valid_symbols, HTML_TEXT, count) && scan_html_text(scanner, lexer, is_cfquery_context, valid_symbols, count)) {
return true;
}
switch (lexer->lookahead) {
case ';':
return false;
break;
case '<':
lexer->mark_end(lexer);
advance(lexer);
if (VS(valid_symbols, CFML_COMMENT, count) && lexer->lookahead == '!') {
advance(lexer);
return scan_comment(lexer, is_cfquery_context);
}
if (implicit_cf_end_tag_valid(valid_symbols, count)) {
return scan_implicit_end_tag(scanner, lexer, true, is_cfquery_context, true);
}
if (VS(valid_symbols, IMPLICIT_END_TAG, count)) {
return scan_implicit_end_tag(scanner, lexer, false, is_cfquery_context, true);
}
break;
case '\0':
if (implicit_cf_end_tag_valid(valid_symbols, count)) {
return scan_implicit_end_tag(scanner, lexer, true, is_cfquery_context, true);
}
if (VS(valid_symbols, IMPLICIT_END_TAG, count)) {
return scan_implicit_end_tag(scanner, lexer, false, is_cfquery_context, true);
}
break;
case '/':
advance(lexer);
if (lexer->lookahead == '>') {
if (VS(valid_symbols, CF_SELF_CLOSING_TAG_DELIMITER, count)) {
return scan_cf_self_closing_tag_delimiter(scanner, lexer, false, is_cfquery_context);
}
if (VS(valid_symbols, CF_SELF_CLOSING_VOID_TAG_DELIMITER, count)) {
return scan_cf_self_closing_tag_delimiter(scanner, lexer, true, is_cfquery_context);
}
if (VS(valid_symbols, SELF_CLOSING_TAG_DELIMITER, count)) {
return scan_self_closing_tag_delimiter(scanner, lexer, is_cfquery_context);
}
if (VS(valid_symbols, CLOSE_CF_TAG_DELIM, count)) {
return scan_closetag_delim(scanner, lexer, true, is_cfquery_context);
}
if (VS(valid_symbols, CLOSE_TAG_DELIM, count)) {
return scan_closetag_delim(scanner, lexer, false, is_cfquery_context);
}
} else if (lexer->lookahead == '/' || lexer->lookahead == '*') {
if (!scan_script_comment(lexer, is_cfquery_context)) {
return false;
}
}
break;
default:
if (valid_start_tag_name(valid_symbols, count) && no_content_symbols(valid_symbols, count)) {
return scan_start_tag_name(scanner, lexer, valid_cf_start_tag_name(valid_symbols, count), is_cfquery_context);
}
if (valid_end_tag_name(valid_symbols, count) && no_content_symbols(valid_symbols, count)) {
return scan_end_tag_name(scanner, lexer, valid_cf_end_tag_name(valid_symbols, count), is_cfquery_context);
}
if (VS(valid_symbols, IMPLICIT_END_TAG, count)) {
return scan_implicit_end_tag(scanner, lexer, false, is_cfquery_context, false);
} else if (VS(valid_symbols, IMPLICIT_CF_END_TAG, count)) {
return scan_implicit_end_tag(scanner, lexer, true, is_cfquery_context, false);
}
if (VS(valid_symbols, ERRONEOUS_END_TAG_NAME, count)) {
return scan_end_tag_name(scanner, lexer, false, is_cfquery_context);
} else if (VS(valid_symbols, ERRONEOUS_CF_END_TAG_NAME, count)) {
return scan_end_tag_name(scanner, lexer, true, is_cfquery_context);
}
if (VS(valid_symbols, CF_COMPONENT_END_TAG_NAME, count)) {
return scan_end_tag_name(scanner, lexer, true, is_cfquery_context);
}
if (VS(valid_symbols, CLOSE_CF_TAG_DELIM, count)) {
if (scan_closetag_delim(scanner, lexer, true, is_cfquery_context)) {
return true;
}
}
if (VS(valid_symbols, CLOSE_TAG_DELIM, count)) {
if (scan_closetag_delim(scanner, lexer, false, is_cfquery_context)) {
return true;
}
}
}
if (VS(valid_symbols, AUTOMATIC_SEMICOLON, count)) {
bool scanned_comment = false;
bool ret = scan_automatic_semicolon(lexer, !VS(valid_symbols, LOGICAL_OR, count), &scanned_comment, is_cfquery_context);
if (!ret && !scanned_comment && VS(valid_symbols, TERNARY_QMARK, count) && lexer->lookahead == '?') {
return scan_ternary_qmark(lexer, is_cfquery_context);
}
return ret;
}
if (VS(valid_symbols, TERNARY_QMARK, count) || VS(valid_symbols, ELVIS_OPERATOR, count)) {
return scan_ternary_qmark(lexer, is_cfquery_context);
}
return false;
}