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

1759 lines
62 KiB
C

#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,
// Rules below this point are not included by the cfquery dialect
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, // Semicolon is illegal, ie a syntax error occurred
NO_NEWLINE, // Unclear if semicolon will be legal, continue
ACCEPT, // Semicolon is legal, assuming a comment was encountered
} 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 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 SERIALIZE_TAGS(tags_field, buffer, size, 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); \
if ((size) + sizeof(_count) + sizeof(_count) > TREE_SITTER_SERIALIZATION_BUFFER_SIZE) break; \
(size) += sizeof(_count); \
(size) += sizeof(_count); \
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) >= TREE_SITTER_SERIALIZATION_BUFFER_SIZE) break; \
(buffer)[(size)++] = (char)_tag.type; \
(buffer)[(size)++] = (char)_len; \
strncpy(&(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 >= TREE_SITTER_SERIALIZATION_BUFFER_SIZE) break; \
(buffer)[(size)++] = (char)_tag.type; \
} \
} \
memcpy(&(buffer)[_count_offset], &_serialized, sizeof(_serialized)); \
memcpy(&(buffer)[_count_offset + sizeof(_serialized)], &_count, sizeof(_count)); \
} while(0)
static unsigned serialize(Scanner *scanner, char *buffer, bool is_cfquery_context) {
unsigned size = 0;
SERIALIZE_TAGS(scanner->tags, buffer, size, is_cfquery_context);
SERIALIZE_TAGS(scanner->cf_tags, buffer, size, is_cfquery_context);
if (size + sizeof(scanner->cfoutput_depth) + sizeof(scanner->cfcomponent_depth) + sizeof(scanner->cffunction_depth) < 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, is_cfquery_context) do { \
for (unsigned _i = 0; _i < (tags_field).size; _i++) tag_free(&(tags_field).contents[_i]); \
array_clear(&(tags_field)); \
uint16_t _serialized = 0, _count = 0; \
memcpy(&_serialized, &(buffer)[(size)], sizeof(_serialized)); (size) += sizeof(_serialized); \
memcpy(&_count, &(buffer)[(size)], sizeof(_count)); (size) += sizeof(_count); \
array_reserve(&(tags_field), _count); \
unsigned _iter = 0; \
for (_iter = 0; _iter < _serialized; _iter++) { \
Tag _tag = tag_new(); \
_tag.type = (TagType)(unsigned char)(buffer)[(size)++]; \
if (tag_has_name(_tag.type, is_cfquery_context)) { \
uint16_t _len = (uint8_t)(buffer)[(size)++]; \
array_reserve(&_tag.tag_name, _len); \
_tag.tag_name.size = _len; \
memcpy(_tag.tag_name.contents, &(buffer)[(size)], _len); \
(size) += _len; \
memcpy(&_tag.html_depth, &(buffer)[(size)], sizeof(_tag.html_depth)); \
(size) += sizeof(_tag.html_depth); \
} \
array_push(&(tags_field), _tag); \
} \
for (; _iter < _count; _iter++) array_push(&(tags_field), tag_new()); \
} 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, is_cfquery_context);
DESERIALIZE_TAGS(scanner->cf_tags, buffer, size, 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;
// ColdFusion tags might start with 'C', ie. <cfcomponent or <cfcontinue
if ( lexer->lookahead == 'c' || lexer->lookahead == 'C' ) {
array_push(&tag_name, towupper(lexer->lookahead));
advance(lexer);
if (lexer->lookahead == 'f' || lexer->lookahead == 'F') {
is_cf_tag = true;
array_delete(&tag_name);
advance(lexer);
}
}
while (( iswalnum(lexer->lookahead) || lexer->lookahead == '-' || lexer->lookahead == '_' || lexer->lookahead == ':' )) {
array_push(&tag_name, towupper(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);
// IE conditional comments: <!--[if ...]>...<![endif]--> or <!--<![endif]-->
// These don't nest — just scan until -->
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 (iswspace(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 bool scan_html_text(Scanner *scanner, TSLexer *lexer, bool is_cfquery_context) {
// Check if we're inside a script/style tag
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;
}
}
// saw_text will be true if we see any non-whitespace content, or any whitespace content that is not a newline and
// does not immediately follow a newline.
bool saw_text = false;
// at_newline will be true if we are currently at a newline, or if we are at whitespace that is not a newline but
// immediately follows a newline.
bool at_newline = false;
bool saw_any = false;
if (in_script_style) {
// Inside script/style: consume until #, <cf, </cf, or </script|</style
lexer->mark_end(lexer);
while (lexer->lookahead != 0 && lexer->lookahead != '#') {
if (lexer->lookahead == '<') {
// Peek for <cf or </cf or </script|</style
advance(lexer);
if (towupper(lexer->lookahead) == 'C') {
advance(lexer);
if (towupper(lexer->lookahead) == 'F') {
break;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
} else if (lexer->lookahead == '/') {
advance(lexer);
if (towupper(lexer->lookahead) == 'C') {
advance(lexer);
if (towupper(lexer->lookahead) == 'F') {
break;
}
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
} else if (towupper(lexer->lookahead) == 'S') {
// Potential </script or </style - stop
break;
}
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 == '#');
}
while (lexer->lookahead != 0 && lexer->lookahead != '<' && lexer->lookahead != '>' && lexer->lookahead != '{' &&
lexer->lookahead != '}' && lexer->lookahead != '#') {
if (lexer->lookahead == '&') {
// Peek ahead to determine if this is an entity
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == '#') {
// Could be numeric entity (&#digits; or &#xhex;) or &# followed by CFML hash
// Mark end after & so it's consumed as text
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
advance(lexer);
if (lexer->lookahead == 'x' || lexer->lookahead == 'X' || iswdigit(lexer->lookahead)) {
// Numeric entity - consume fully as text
if (lexer->lookahead == 'x' || lexer->lookahead == 'X') {
advance(lexer);
while (iswxdigit(lexer->lookahead)) advance(lexer);
} else {
while (iswdigit(lexer->lookahead)) advance(lexer);
}
if (lexer->lookahead == ';') advance(lexer);
lexer->mark_end(lexer);
continue;
}
// &# not followed by digit/x - & consumed as text, break at #
break;
}
if (iswalpha(lexer->lookahead)) {
// Could be &word; - scan ahead for ;
unsigned count = 0;
while (iswalpha(lexer->lookahead) && count < 31) {
advance(lexer);
count++;
}
if (lexer->lookahead == ';' && count > 0) {
// Valid entity pattern - stop before &
break;
}
// Not a valid entity - consume as text
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
}
// & followed by non-alpha, non-# - consume as text
lexer->mark_end(lexer);
saw_text = true;
saw_any = true;
continue;
}
bool is_wspace = iswspace(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->result_symbol = HTML_TEXT;
// Emit whitespace-only content when the next char is '#' so the parser
// advances to a state where hash tokens become valid.
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);
}
//*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;
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 (towupper(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 (towupper(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 (towupper(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;
// Default to cfml
unsigned result = CF_SAVECONTENT_BODY_CFML;
// Mark end at current position - this is a zero-width token
lexer->mark_end(lexer);
// Peek ahead for <!--- @content TYPE --->
// Skip whitespace first
while (iswspace(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);
// Skip whitespace after <!---
while (iswspace(lexer->lookahead)) advance(lexer);
// Check for @content
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) {
// Skip whitespace
while (iswspace(lexer->lookahead)) advance(lexer);
// Read type word
char type_buf[16];
int len = 0;
while (iswalpha(lexer->lookahead) && len < 15) {
type_buf[len++] = towlower(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 (towupper(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";
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) {
// CFML boundary checks (only when not mid-delimiter match)
if (stop_at_cfml && delimiter_index == 0) {
if (lexer->lookahead == '#' && scanner->cfoutput_depth > 0) {
break;
}
if (lexer->lookahead == '<') {
advance(lexer);
if (towupper(lexer->lookahead) == 'C') {
advance(lexer);
if (towupper(lexer->lookahead) == 'F') {
// Stop before <cf
break;
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else if (lexer->lookahead == '/') {
advance(lexer);
if (towupper(lexer->lookahead) == 'C') {
advance(lexer);
if (towupper(lexer->lookahead) == 'F') {
// Stop before </cf
break;
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else if (towupper(lexer->lookahead) == end_delimiter[2]) {
// Potential </script or </style
delimiter_index = 3;
advance(lexer);
continue;
}
lexer->mark_end(lexer);
has_content = true;
continue;
} else {
lexer->mark_end(lexer);
has_content = true;
continue;
}
}
}
if (towupper(lexer->lookahead) == end_delimiter[delimiter_index]) {
delimiter_index++;
if (delimiter_index == strlen(end_delimiter)) {
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 {
// Void tag processing
if (!is_cf_context && parent && tag_is_void(parent)) {
pop_tag(scanner, false);
lexer->result_symbol = IMPLICIT_END_TAG;
return true;
}
// Void tag processing
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) {
// Check if there's a CF_IF on the cf_tags stack whose html_depth
// is less than current tags.size (meaning HTML tags opened inside cfif)
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) {
// A CF closing tag in HTML context: only implicitly close HTML tags
// that were opened inside this CF tag (i.e. tags.size > cf html_depth)
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) {
// The tag correctly closes the topmost element on the stack
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))) {
// Before accepting the CF close, check if HTML tags opened inside
// this CF tag need implicit closing (e.g. <cfloop><span></cfloop>)
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 ? (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))) {
// pop_tag(scanner, is_cf);
// lexer->result_symbol = is_cf ? IMPLICIT_CF_END_TAG : IMPLICIT_END_TAG;
// tag_free(&next_tag);
// return true;
// }
// Otherwise, dig deeper and queue implicit end tags (to be nice in
// the case of malformed HTML)
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) {
// Dynamic tag name: <#expression#>
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;
}
// bool is_cf = result.is_cf_tag || is_cf_context;
Tag tag = is_cf_context ? cf_tag_for_name(result.tag_name) : tag_for_name(result.tag_name);
// printf("scan_start_tag_name: tag=%.*s, is_cf_tag=%d, is_cf_context=%d, type=%d\n",
// (int)result.tag_name.size, result.tag_name.contents, result.is_cf_tag, is_cf_context, tag.type);
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;
tag_free(&tag); /* not pushed to the stack — free its owned tag_name */
return true;
case CF_RETURN:
lexer->result_symbol = CF_RETURN_START_TAG_NAME;
tag_free(&tag); /* not pushed to the stack — free its owned 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;
tag_free(&tag); /* not pushed to the stack — free its owned tag_name */
return true;
case CF_ELSE:
lexer->result_symbol = CF_ELSE_TAG_NAME;
tag_free(&tag); /* not pushed to the stack — free its owned 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.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) {
// Dynamic closing tag: </#expression#>
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;
}
// printf("scan_end_tag_name: tag=%.*s, is_cf_context=%d, tags.size=%d, cf_tags.size=%d\n",
// (int)result.tag_name.size, result.tag_name.contents, is_cf_context,
// scanner->tags.size, scanner->cf_tags.size);
// bool is_cf = result.is_cf_tag || is_cf_context;
Tag tag = is_cf_context ? cf_tag_for_name(result.tag_name) : tag_for_name(result.tag_name);
// cfcomponent is a void tag (never pushed); just decrement depth
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;
}
// Determine the minimum HTML stack index we can search to.
// CF control-flow tags record html_depth when pushed; don't search below that.
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 {
// Search deeper in the stack for a matching tag
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;
}
// Check if the current position matches a CFML word operator (case-insensitive).
static bool scan_cfml_word_operator(TSLexer *lexer) {
char buf[11] = {0};
int len = 0;
for (; len < 10 && iswalpha(lexer->lookahead); len++) {
buf[len] = towlower(lexer->lookahead);
skip(lexer);
}
bool at_end = !iswalnum(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 (!iswspace(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;
// Insert a semicolon before decimals literals but not otherwise.
case '.':
skip(lexer);
return iswdigit(lexer->lookahead);
// Insert a semicolon before `--` and `++`, but not before binary `+` or `-`.
case '+':
skip(lexer);
return lexer->lookahead == '+';
case '-':
skip(lexer);
return lexer->lookahead == '-';
// Don't insert a semicolon before `!=`, but do insert one before a unary `!`.
case '!':
skip(lexer);
return lexer->lookahead != '=';
// Don't insert a semicolon before CFML word operators
// (and, or, eq, neq, not, gt, gte, ge, lt, lte, le, mod, in, instanceof)
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 (!iswspace(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 (iswdigit(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) {
// Skip whitespace and script-style comments (// and /* */)
for (;;) {
while (iswspace(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;
}
}
// Read the first word
char word[16];
int len = 0;
while (iswalpha(lexer->lookahead) && len < 15) {
word[len++] = towlower(lexer->lookahead);
advance(lexer);
}
word[len] = '\0';
// Must not be followed by another identifier char (e.g. 'componentFoo')
if (iswalnum(lexer->lookahead) || lexer->lookahead == '_') return false;
// If the first word is a modifier, skip whitespace and read the next word
if (strcmp(word, "abstract") == 0 || strcmp(word, "static") == 0) {
while (iswspace(lexer->lookahead)) advance(lexer);
len = 0;
while (iswalpha(lexer->lookahead) && len < 15) {
word[len++] = towlower(lexer->lookahead);
advance(lexer);
}
word[len] = '\0';
if (iswalnum(lexer->lookahead) || lexer->lookahead == '_') return false;
}
if (strcmp(word, "component") != 0 && strcmp(word, "property") != 0 &&
strcmp(word, "interface") != 0 && strcmp(word, "import") != 0) {
return false;
}
// Consume the rest of the file
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 (iswspace(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)) {
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;
}