#include "tree_sitter/parser.h"
#include <stdio.h>
#include <string.h>
#include <wctype.h>
enum TokenType {
AUTOMATIC_SEMICOLON,
TEMPLATE_CHARS,
TERNARY_QMARK,
ELVIS_OPERATOR,
LOGICAL_OR,
REGEX_PATTERN,
QUERY_TEXT,
TAG_LINEFEED,
CFML_TEMPLATE_CONTENT,
CFML_COMMENT,
JAVA_CLASS_CONTENT,
JAVA_BLOCK_OPEN,
STATIC_TYPE_PREFIX
};
void *tree_sitter_cfscript_external_scanner_create() { return NULL; }
void tree_sitter_cfscript_external_scanner_destroy(void *p) {}
unsigned tree_sitter_cfscript_external_scanner_serialize(void *payload, char *buffer) { return 0; }
void tree_sitter_cfscript_external_scanner_deserialize(void *p, const char *b, unsigned n) {}
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_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_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 bool scan_template_chars(TSLexer *lexer) {
lexer->result_symbol = TEMPLATE_CHARS;
for (bool has_content = false;; has_content = true) {
lexer->mark_end(lexer);
switch (lexer->lookahead) {
case '`':
return has_content;
case '\0':
return false;
case '$':
advance(lexer);
if (lexer->lookahead == '{') {
return has_content;
}
break;
case '\\':
return has_content;
default:
advance(lexer);
}
}
}
typedef enum {
REJECT, NO_NEWLINE, ACCEPT, } WhitespaceResult;
static WhitespaceResult scan_whitespace_and_comments(TSLexer *lexer, bool *scanned_comment, bool consume) {
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 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) {
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);
if (result == REJECT) {
return false;
}
if (result == ACCEPT && 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) == 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 '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':
case 'i':
return !scan_cfml_word_operator(lexer);
default:
break;
}
return true;
}
static bool scan_ternary_qmark(TSLexer *lexer) {
for (;;) {
if (!cf_isspace(lexer->lookahead)) {
break;
}
skip(lexer);
}
if (lexer->lookahead == '?') {
advance(lexer);
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
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_query_text(TSLexer *lexer) {
bool saw_text = false;
bool at_newline = false;
while (lexer->lookahead != 0 && lexer->lookahead != '"') {
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;
}
}
advance(lexer);
}
lexer->result_symbol = QUERY_TEXT;
return saw_text;
}
static bool scan_cfml_template_content(TSLexer *lexer) {
lexer->result_symbol = CFML_TEMPLATE_CONTENT;
bool has_content = false;
while (lexer->lookahead != 0) {
if (lexer->lookahead == '`') {
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == '`') {
advance(lexer);
if (lexer->lookahead == '`') {
return has_content;
}
}
has_content = true;
} else {
advance(lexer);
has_content = true;
}
}
return false;
}
static bool scan_cfml_comment(TSLexer *lexer) {
if (lexer->lookahead != '<') return false;
advance(lexer);
if (lexer->lookahead != '!') return false;
advance(lexer);
for (int i = 0; i < 3; i++) {
if (lexer->lookahead != '-') return false;
advance(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) {
lexer->result_symbol = CFML_COMMENT;
lexer->mark_end(lexer);
return true;
}
}
continue;
}
advance(lexer);
}
return false;
}
static bool scan_static_type_prefix_tail(TSLexer *lexer) {
if (lexer->lookahead != ':') return false;
advance(lexer);
if (lexer->lookahead == ':') return false;
lexer->mark_end(lexer);
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') advance(lexer);
for (;;) {
if (!cf_isalpha(lexer->lookahead) && lexer->lookahead != '_') return false;
while (cf_isalnum(lexer->lookahead) || lexer->lookahead == '_') advance(lexer);
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') advance(lexer);
if (lexer->lookahead == '.') {
advance(lexer);
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') advance(lexer);
continue;
}
break;
}
if (lexer->lookahead != ':') return false;
advance(lexer);
return lexer->lookahead == ':';
}
static bool scan_java_block_open_tail(TSLexer *lexer) {
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') advance(lexer);
if (lexer->lookahead != '{') return false;
advance(lexer);
lexer->mark_end(lexer);
for (;;) {
while (cf_isspace(lexer->lookahead)) advance(lexer);
if (lexer->lookahead != '/') break;
advance(lexer);
if (lexer->lookahead == '/') {
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; }
}
if (lexer->lookahead == '@') { lexer->result_symbol = JAVA_BLOCK_OPEN;
return true;
}
char buf[16];
int len = 0;
while (cf_isalpha(lexer->lookahead) && len < 15) {
buf[len++] = (char)cf_tolower(lexer->lookahead);
advance(lexer);
}
buf[len] = '\0';
static const char *starters[] = {
"public", "private", "protected", "static", "final", "abstract",
"class", "interface", "enum", "record", "import", "package", "strictfp"
};
for (unsigned i = 0; i < sizeof(starters) / sizeof(starters[0]); i++) {
if (strcmp(buf, starters[i]) == 0) {
lexer->result_symbol = JAVA_BLOCK_OPEN;
return true;
}
}
return false;
}
static bool scan_java_class_content(TSLexer *lexer) {
unsigned depth = 1;
bool any = false;
for (;;) {
if (lexer->lookahead == 0) {
lexer->mark_end(lexer);
lexer->result_symbol = JAVA_CLASS_CONTENT;
return any;
}
if (lexer->lookahead == '"' || lexer->lookahead == '\'') {
int32_t quote = lexer->lookahead;
advance(lexer);
any = true;
while (lexer->lookahead != 0 && lexer->lookahead != quote) {
if (lexer->lookahead == '\\') {
advance(lexer);
if (lexer->lookahead == 0) break;
}
advance(lexer);
}
if (lexer->lookahead == quote) advance(lexer);
continue;
}
if (lexer->lookahead == '/') {
advance(lexer);
any = true;
if (lexer->lookahead == '/') {
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);
}
}
}
continue;
}
if (lexer->lookahead == '{') {
depth++;
advance(lexer);
any = true;
continue;
}
if (lexer->lookahead == '}') {
if (depth == 1) {
lexer->mark_end(lexer);
lexer->result_symbol = JAVA_CLASS_CONTENT;
return any;
}
depth--;
advance(lexer);
any = true;
continue;
}
advance(lexer);
any = true;
}
}
static bool scan_java_or_cfml_word(TSLexer *lexer, const bool *valid_symbols, bool skip_space) {
if (skip_space) {
while (cf_isspace(lexer->lookahead)) skip(lexer);
}
const char *word;
if (cf_tolower(lexer->lookahead) == 'j') {
word = "java";
} else if (cf_tolower(lexer->lookahead) == 'c') {
word = "cfml";
} else {
return false;
}
for (int i = 0; i < 4; i++) {
if (cf_tolower(lexer->lookahead) != word[i]) return false;
advance(lexer);
}
if (cf_isalnum(lexer->lookahead) || lexer->lookahead == '_') return false;
if (lexer->lookahead == ':') {
if (!valid_symbols[STATIC_TYPE_PREFIX]) return false;
if (!scan_static_type_prefix_tail(lexer)) return false;
lexer->result_symbol = STATIC_TYPE_PREFIX;
return true;
}
if (word[0] == 'j' && valid_symbols[JAVA_BLOCK_OPEN] &&
!valid_symbols[AUTOMATIC_SEMICOLON]) {
return scan_java_block_open_tail(lexer);
}
return false;
}
bool tree_sitter_cfscript_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
if (valid_symbols[JAVA_CLASS_CONTENT]) {
if (valid_symbols[AUTOMATIC_SEMICOLON]) {
return false;
}
return scan_java_class_content(lexer);
}
if (valid_symbols[CFML_TEMPLATE_CONTENT]) {
if (valid_symbols[AUTOMATIC_SEMICOLON]) {
return false;
}
return scan_cfml_template_content(lexer);
}
if (valid_symbols[TEMPLATE_CHARS]) {
if (valid_symbols[AUTOMATIC_SEMICOLON]) {
return false;
}
return scan_template_chars(lexer);
}
if (valid_symbols[QUERY_TEXT] && scan_query_text(lexer)) {
return true;
}
if (valid_symbols[CFML_COMMENT] && !valid_symbols[AUTOMATIC_SEMICOLON] &&
!valid_symbols[TERNARY_QMARK] && !valid_symbols[ELVIS_OPERATOR]) {
while (cf_isspace(lexer->lookahead)) skip(lexer);
if (lexer->lookahead == '<' && scan_cfml_comment(lexer)) {
return true;
}
if (valid_symbols[STATIC_TYPE_PREFIX] &&
scan_java_or_cfml_word(lexer, valid_symbols, false)) {
return true;
}
return false;
}
if (valid_symbols[AUTOMATIC_SEMICOLON] && !valid_symbols[TAG_LINEFEED]) {
bool scanned_comment = false;
bool ret = scan_automatic_semicolon(lexer, !valid_symbols[LOGICAL_OR], &scanned_comment);
if (!ret && !scanned_comment && valid_symbols[TERNARY_QMARK] && lexer->lookahead == '?') {
return scan_ternary_qmark(lexer);
}
if (!ret && valid_symbols[CFML_COMMENT] && lexer->lookahead == '<' && scan_cfml_comment(lexer)) {
return true;
}
return ret;
}
if (valid_symbols[AUTOMATIC_SEMICOLON] && valid_symbols[TAG_LINEFEED]) {
lexer->mark_end(lexer);
while (cf_isspace(lexer->lookahead)) skip(lexer);
if (lexer->lookahead == '}' || lexer->lookahead == 0) {
lexer->result_symbol = AUTOMATIC_SEMICOLON;
return true;
}
}
if (valid_symbols[TERNARY_QMARK] || valid_symbols[ELVIS_OPERATOR]) {
return scan_ternary_qmark(lexer);
}
if ((valid_symbols[JAVA_BLOCK_OPEN] || valid_symbols[STATIC_TYPE_PREFIX]) &&
scan_java_or_cfml_word(lexer, valid_symbols, true)) {
return true;
}
return false;
}