From 1e1d40851c85927ee8055bc3b379dea93662df9a Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 1 Jun 2026 18:28:36 +0200 Subject: [PATCH] feat(lang): add CFML / ColdFusion support (cfscript + tag dialects) Vendor the MIT-licensed cfmleditor/tree-sitter-cfml grammars and wire both CFML dialects through the pipeline: - .cfc components -> cfscript grammar (CBM_LANG_CFSCRIPT): a JS-like grammar, so function/method declarations, calls and imports extract with the shared JS/TS arrays. Components' functions become Function nodes. - .cfm templates -> cfml tag grammar (CBM_LANG_CFML): tag-based (HTML-derived). Embedded functions extract via function_declaration; tag-based nodes are handled by a dedicated walker branch that reads the name attribute (cf_function_tag has no name field). Shared scanner headers are vendored at vendored/common/ so the grammar's ../../common/ includes resolve without editing vendored sources. Adds an ASCII case-insensitive compare for CFML's case-insensitive attributes. Tests cover both dialects and the extension mapping. LICENSEs vendored. Closes #38 --- internal/cbm/cbm.h | 2 + internal/cbm/extract_defs.c | 66 + internal/cbm/grammar_cfml.c | 6 + internal/cbm/grammar_cfscript.c | 4 + internal/cbm/lang_specs.c | 35 + internal/cbm/vendored/common/scanner.h | 1754 + internal/cbm/vendored/common/tag.h | 447 + .../cbm/vendored/common/tree_sitter/alloc.h | 54 + .../cbm/vendored/common/tree_sitter/array.h | 330 + .../cbm/vendored/common/tree_sitter/parser.h | 286 + internal/cbm/vendored/grammars/cfml/LICENSE | 21 + internal/cbm/vendored/grammars/cfml/parser.c | 312930 +++++++++++++ internal/cbm/vendored/grammars/cfml/scanner.c | 34 + .../grammars/cfml/tree_sitter/alloc.h | 54 + .../grammars/cfml/tree_sitter/array.h | 330 + .../grammars/cfml/tree_sitter/parser.h | 286 + .../cbm/vendored/grammars/cfscript/LICENSE | 21 + .../cbm/vendored/grammars/cfscript/parser.c | 356138 +++++++++++++++ .../cbm/vendored/grammars/cfscript/scanner.c | 456 + internal/cbm/vendored/grammars/cfscript/tag.h | 385 + .../grammars/cfscript/tree_sitter/alloc.h | 54 + .../grammars/cfscript/tree_sitter/array.h | 330 + .../grammars/cfscript/tree_sitter/parser.h | 286 + src/discover/language.c | 6 + tests/test_extraction.c | 36 + tests/test_language.c | 7 + 26 files changed, 674358 insertions(+) create mode 100644 internal/cbm/grammar_cfml.c create mode 100644 internal/cbm/grammar_cfscript.c create mode 100644 internal/cbm/vendored/common/scanner.h create mode 100644 internal/cbm/vendored/common/tag.h create mode 100644 internal/cbm/vendored/common/tree_sitter/alloc.h create mode 100644 internal/cbm/vendored/common/tree_sitter/array.h create mode 100644 internal/cbm/vendored/common/tree_sitter/parser.h create mode 100644 internal/cbm/vendored/grammars/cfml/LICENSE create mode 100644 internal/cbm/vendored/grammars/cfml/parser.c create mode 100644 internal/cbm/vendored/grammars/cfml/scanner.c create mode 100644 internal/cbm/vendored/grammars/cfml/tree_sitter/alloc.h create mode 100644 internal/cbm/vendored/grammars/cfml/tree_sitter/array.h create mode 100644 internal/cbm/vendored/grammars/cfml/tree_sitter/parser.h create mode 100644 internal/cbm/vendored/grammars/cfscript/LICENSE create mode 100644 internal/cbm/vendored/grammars/cfscript/parser.c create mode 100644 internal/cbm/vendored/grammars/cfscript/scanner.c create mode 100644 internal/cbm/vendored/grammars/cfscript/tag.h create mode 100644 internal/cbm/vendored/grammars/cfscript/tree_sitter/alloc.h create mode 100644 internal/cbm/vendored/grammars/cfscript/tree_sitter/array.h create mode 100644 internal/cbm/vendored/grammars/cfscript/tree_sitter/parser.h diff --git a/internal/cbm/cbm.h b/internal/cbm/cbm.h index cee4e21ed..94462faf1 100644 --- a/internal/cbm/cbm.h +++ b/internal/cbm/cbm.h @@ -168,6 +168,8 @@ typedef enum { CBM_LANG_K8S, // Generic Kubernetes manifest (apiVersion: detected) CBM_LANG_PINE, // Pine Script (TradingView indicator / strategy language) CBM_LANG_QML, // Qt QML (Qt Modeling Language — declarative UI + embedded JS) + CBM_LANG_CFSCRIPT, // CFML script dialect (.cfc components — Lucee/ColdFusion) + CBM_LANG_CFML, // CFML tag dialect (.cfm templates — Lucee/ColdFusion) CBM_LANG_COUNT } CBMLanguage; diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 5e049ae9d..ab71eb622 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -3613,6 +3613,67 @@ static void push_class_body_children(TSNode node, const CBMLangSpec *spec, walk_ } } +// ASCII case-insensitive equality vs a lowercase literal (CFML attribute names +// are case-insensitive, e.g. NAME / Name / name). +static bool ascii_ci_equals(const char *s, const char *lower_lit) { + if (!s) { + return false; + } + for (; *s && *lower_lit; s++, lower_lit++) { + if (tolower((unsigned char)*s) != (unsigned char)*lower_lit) { + return false; + } + } + return *s == '\0' && *lower_lit == '\0'; +} + +// CFML tag function: ... (#38). The +// cf_function_tag node has no `name` field — the name lives in a cf_attribute +// child (name="foo"). Emit a Function node named after that attribute value. +static void extract_cfml_function_tag(CBMExtractCtx *ctx, TSNode node) { + CBMArena *a = ctx->arena; + char *name = NULL; + uint32_t cc = ts_node_named_child_count(node); + for (uint32_t i = 0; i < cc && !name; i++) { + TSNode ch = ts_node_named_child(node, i); + if (strcmp(ts_node_type(ch), "cf_attribute") != 0) { + continue; + } + TSNode an = cbm_find_child_by_kind(ch, "cf_attribute_name"); + if (ts_node_is_null(an)) { + continue; + } + char *aname = cbm_node_text(a, an, ctx->source); + if (!ascii_ci_equals(aname, "name")) { + continue; + } + TSNode val = cbm_find_child_by_kind(ch, "quoted_cf_attribute_value"); + if (ts_node_is_null(val)) { + val = cbm_find_child_by_kind(ch, "cf_attribute_value"); + } + if (ts_node_is_null(val)) { + continue; + } + TSNode inner = cbm_find_child_by_kind(val, "attribute_value"); + name = cbm_node_text(a, ts_node_is_null(inner) ? val : inner, ctx->source); + } + if (!name || !name[0]) { + return; + } + + CBMDefinition def; + memset(&def, 0, sizeof(def)); + def.name = name; + def.qualified_name = cbm_fqn_compute(a, ctx->project, ctx->rel_path, name); + def.label = "Function"; + def.file_path = ctx->rel_path; + def.start_line = ts_node_start_point(node).row + TS_LINE_OFFSET; + def.end_line = ts_node_end_point(node).row + TS_LINE_OFFSET; + def.lines = (int)(def.end_line - def.start_line + TS_LINE_OFFSET); + def.is_exported = true; + cbm_defs_push(&ctx->result->defs, a, def); +} + // Languages that use the C preprocessor and therefore have #define macros. static bool is_c_preprocessor_lang(CBMLanguage lang) { return lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA || @@ -3677,6 +3738,11 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec, continue; // the macro body is a preproc_arg — nothing more to extract } + if (ctx->language == CBM_LANG_CFML && strcmp(kind, "cf_function_tag") == 0) { + extract_cfml_function_tag(ctx, node); + // fall through: descend into the body for nested tags / calls + } + if (cbm_kind_in_set(node, spec->function_node_types)) { if (!is_template_class_node(node, ctx->language)) { extract_func_def(ctx, node, spec); diff --git a/internal/cbm/grammar_cfml.c b/internal/cbm/grammar_cfml.c new file mode 100644 index 000000000..65ceada4d --- /dev/null +++ b/internal/cbm/grammar_cfml.c @@ -0,0 +1,6 @@ +// Vendored tree-sitter grammar: cfml (CFML tag dialect — .cfm templates) +// Each grammar compiled as separate unit (conflicting static symbols). +// scanner.c pulls in the shared ../../common/scanner.h (and tag.h), which is +// all-static; tag.c is therefore NOT included here (it would redefine it). +#include "vendored/grammars/cfml/parser.c" +#include "vendored/grammars/cfml/scanner.c" diff --git a/internal/cbm/grammar_cfscript.c b/internal/cbm/grammar_cfscript.c new file mode 100644 index 000000000..a70b0e502 --- /dev/null +++ b/internal/cbm/grammar_cfscript.c @@ -0,0 +1,4 @@ +// Vendored tree-sitter grammar: cfscript (CFML script dialect — .cfc components) +// Each grammar compiled as separate unit (conflicting static symbols). +#include "vendored/grammars/cfscript/parser.c" +#include "vendored/grammars/cfscript/scanner.c" diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 8713daa4c..fadc059c7 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -99,6 +99,8 @@ extern const TSLanguage *tree_sitter_hare(void); extern const TSLanguage *tree_sitter_pony(void); extern const TSLanguage *tree_sitter_luau(void); extern const TSLanguage *tree_sitter_qmljs(void); +extern const TSLanguage *tree_sitter_cfscript(void); +extern const TSLanguage *tree_sitter_cfml(void); extern const TSLanguage *tree_sitter_janet_simple(void); extern const TSLanguage *tree_sitter_sway(void); extern const TSLanguage *tree_sitter_nasm(void); @@ -264,6 +266,26 @@ static const char *qml_field_types[] = {"ui_property", "ui_signal", "public_fiel NULL}; static const char *qml_import_types[] = {"import_statement", "import", "ui_import", NULL}; +// ==================== CFScript (CFML .cfc script dialect) ==================== +// JS-like grammar: components contain function/method declarations. Reuses the +// JS call/branch/var/module arrays. +static const char *cfscript_func_types[] = {"function_declaration", "function_expression", + "arrow_function", "method_definition", NULL}; +static const char *cfscript_field_types[] = {"property_declaration", NULL}; +static const char *cfscript_import_types[] = {"import_statement", "import", NULL}; + +// ==================== CFML (tag dialect — .cfm templates) ==================== +// Tag-based grammar (HTML-derived). Embedded functions appear as +// function_declaration/function_expression; tag nodes +// (cf_function_tag) are handled separately in the definition walker because +// their name lives in a cf_attribute rather than a `name` field. +static const char *cfml_func_types[] = {"function_declaration", "function_expression", NULL}; +static const char *cfml_call_types[] = {"call_expression", NULL}; +static const char *cfml_branch_types[] = { + "cf_if_tag", "cf_elseif_tag", "cf_else_tag", "if_statement", + "for_statement", "while_statement", "switch_statement", NULL}; +static const char *cfml_module_types[] = {"program", "component_file", NULL}; + // ==================== RUST ==================== static const char *rust_func_types[] = {"function_item", "function_signature_item", "closure_expression", NULL}; @@ -1960,6 +1982,19 @@ static const CBMLangSpec lang_specs[CBM_LANG_COUNT] = { (const char *[]){"assignment_expression", "augmented_assignment_expression", NULL}, js_throw_types, NULL, ts_decorator_types, NULL, NULL, tree_sitter_qmljs, NULL}, + // CBM_LANG_CFSCRIPT + [CBM_LANG_CFSCRIPT] = + {CBM_LANG_CFSCRIPT, cfscript_func_types, empty_types, cfscript_field_types, js_module_types, + js_call_types, cfscript_import_types, cfscript_import_types, js_branch_types, js_var_types, + (const char *[]){"assignment_expression", "augmented_assignment_expression", NULL}, + js_throw_types, NULL, empty_types, NULL, NULL, tree_sitter_cfscript, NULL}, + + // CBM_LANG_CFML + [CBM_LANG_CFML] = {CBM_LANG_CFML, cfml_func_types, empty_types, empty_types, cfml_module_types, + cfml_call_types, empty_types, empty_types, cfml_branch_types, empty_types, + empty_types, empty_types, NULL, empty_types, NULL, NULL, tree_sitter_cfml, + NULL}, + // CBM_LANG_GLEAM [CBM_LANG_GLEAM] = {CBM_LANG_GLEAM, gleam_func_types, gleam_class_types, gleam_field_types, gleam_module_types, gleam_call_types, gleam_import_types, empty_types, diff --git a/internal/cbm/vendored/common/scanner.h b/internal/cbm/vendored/common/scanner.h new file mode 100644 index 000000000..a04ea12f1 --- /dev/null +++ b/internal/cbm/vendored/common/scanner.h @@ -0,0 +1,1754 @@ +#include "tag.h" +#include "tree_sitter/parser.h" + +#include +#include +#include + +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. 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: or + // 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 #, mark_end(lexer); + while (lexer->lookahead != 0 && lexer->lookahead != '#') { + if (lexer->lookahead == '<') { + // Peek for 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 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, "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, "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, "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 + // 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 "; + + if (lexer->lookahead == '<') { + for (unsigned i = 0; i < 4; i++) { + if (lexer->lookahead != comment_start[i]) { + return false; + } + advance(lexer); + } + } else if (lexer->lookahead == '-') { + for (unsigned i = 0; i < 3; i++) { + if (lexer->lookahead != comment_end[i]) { + return false; + } + advance(lexer); + } + } else { + return false; + } + + while (lexer->lookahead != 0 && lexer->lookahead != '\n' && lexer->lookahead != 0x2028 && + lexer->lookahead != 0x2029) { + advance(lexer); + } + + lexer->result_symbol = HTML_COMMENT; + lexer->mark_end(lexer); + + return true; +} + */ + + /* +static bool scan_jsx_text(TSLexer *lexer) { + // 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; + + while (lexer->lookahead != 0 && lexer->lookahead != '<' && lexer->lookahead != '>' && lexer->lookahead != '{' && + lexer->lookahead != '}' && lexer->lookahead != '&') { + bool is_wspace = iswspace(lexer->lookahead); + if (lexer->lookahead == '\n') { + at_newline = true; + } else { + // If at_newline is already true, and we see some whitespace, then it must stay true. + // Otherwise, it should be false. + // + // See the table below to determine the logic for computing `saw_text`. + // + // |------------------------------------| + // | at_newline | is_wspace | saw_text | + // |------------|-----------|-----------| + // | false (0) | false (0) | true (1) | + // | false (0) | true (1) | true (1) | + // | true (1) | false (0) | true (1) | + // | true (1) | true (1) | false (0) | + // |------------------------------------| + + at_newline &= is_wspace; + if (!at_newline) { + saw_text = true; + } + } + + advance(lexer); + } + + lexer->result_symbol = JSX_TEXT; + return saw_text; +} +*/ + +static bool scan_query_text(TSLexer *lexer) { + + bool saw_text = false; + bool at_newline = false; + + while (lexer->lookahead != '"') { + bool is_wspace = iswspace(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; +} + +bool tree_sitter_cfscript_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) { + if (valid_symbols[CFML_TEMPLATE_CONTENT]) { + 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[JSX_TEXT] && scan_jsx_text(lexer)) { + // return true; + // } + + if (valid_symbols[QUERY_TEXT] && scan_query_text(lexer)) { + return true; + } + + 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); + } + return ret; + } + + if (valid_symbols[TERNARY_QMARK] || valid_symbols[ELVIS_OPERATOR]) { + return scan_ternary_qmark(lexer); + } + + /*if (valid_symbols[HTML_COMMENT] && !valid_symbols[LOGICAL_OR] && + !valid_symbols[REGEX_PATTERN]) { + return scan_html_comment(lexer); + }*/ + + return false; +} \ No newline at end of file diff --git a/internal/cbm/vendored/grammars/cfscript/tag.h b/internal/cbm/vendored/grammars/cfscript/tag.h new file mode 100644 index 000000000..77f17959c --- /dev/null +++ b/internal/cbm/vendored/grammars/cfscript/tag.h @@ -0,0 +1,385 @@ +#include "tree_sitter/array.h" + +#include + +typedef enum { + AREA, + BASE, + BASEFONT, + BGSOUND, + BR, + COL, + COMMAND, + EMBED, + FRAME, + HR, + IMAGE, + IMG, + INPUT, + ISINDEX, + KEYGEN, + LINK, + MENUITEM, + META, + NEXTID, + PARAM, + SOURCE, + TRACK, + WBR, + END_OF_VOID_TAGS, + + A, + ABBR, + ADDRESS, + ARTICLE, + ASIDE, + AUDIO, + B, + BDI, + BDO, + BLOCKQUOTE, + BODY, + BUTTON, + CANVAS, + CAPTION, + CITE, + CODE, + COLGROUP, + DATA, + DATALIST, + DD, + DEL, + DETAILS, + DFN, + DIALOG, + DIV, + DL, + DT, + EM, + FIELDSET, + FIGCAPTION, + FIGURE, + FOOTER, + FORM, + H1, + H2, + H3, + H4, + H5, + H6, + HEAD, + HEADER, + HGROUP, + HTML, + I, + IFRAME, + INS, + KBD, + LABEL, + LEGEND, + LI, + MAIN, + MAP, + MARK, + MATH, + MENU, + METER, + NAV, + NOSCRIPT, + OBJECT, + OL, + OPTGROUP, + OPTION, + OUTPUT, + P, + PICTURE, + PRE, + PROGRESS, + Q, + RB, + RP, + RT, + RTC, + RUBY, + S, + SAMP, + SCRIPT, + SECTION, + SELECT, + SLOT, + SMALL, + SPAN, + STRONG, + STYLE, + SUB, + SUMMARY, + SUP, + SVG, + TABLE, + TBODY, + TD, + TEMPLATE, + TEXTAREA, + TFOOT, + TH, + THEAD, + TIME, + TITLE, + TR, + U, + UL, + VAR, + VIDEO, + + CUSTOM, + END_, +} TagType; + +typedef Array(char) String; + +typedef struct { + char tag_name[16]; + TagType tag_type; +} TagMapEntry; + +typedef struct { + TagType type; + String custom_tag_name; +} Tag; + +static const TagMapEntry TAG_TYPES_BY_TAG_NAME[126] = { + {"AREA", AREA }, + {"BASE", BASE }, + {"BASEFONT", BASEFONT }, + {"BGSOUND", BGSOUND }, + {"BR", BR }, + {"COL", COL }, + {"COMMAND", COMMAND }, + {"EMBED", EMBED }, + {"FRAME", FRAME }, + {"HR", HR }, + {"IMAGE", IMAGE }, + {"IMG", IMG }, + {"INPUT", INPUT }, + {"ISINDEX", ISINDEX }, + {"KEYGEN", KEYGEN }, + {"LINK", LINK }, + {"MENUITEM", MENUITEM }, + {"META", META }, + {"NEXTID", NEXTID }, + {"PARAM", PARAM }, + {"SOURCE", SOURCE }, + {"TRACK", TRACK }, + {"WBR", WBR }, + {"A", A }, + {"ABBR", ABBR }, + {"ADDRESS", ADDRESS }, + {"ARTICLE", ARTICLE }, + {"ASIDE", ASIDE }, + {"AUDIO", AUDIO }, + {"B", B }, + {"BDI", BDI }, + {"BDO", BDO }, + {"BLOCKQUOTE", BLOCKQUOTE}, + {"BODY", BODY }, + {"BUTTON", BUTTON }, + {"CANVAS", CANVAS }, + {"CAPTION", CAPTION }, + {"CITE", CITE }, + {"CODE", CODE }, + {"COLGROUP", COLGROUP }, + {"DATA", DATA }, + {"DATALIST", DATALIST }, + {"DD", DD }, + {"DEL", DEL }, + {"DETAILS", DETAILS }, + {"DFN", DFN }, + {"DIALOG", DIALOG }, + {"DIV", DIV }, + {"DL", DL }, + {"DT", DT }, + {"EM", EM }, + {"FIELDSET", FIELDSET }, + {"FIGCAPTION", FIGCAPTION}, + {"FIGURE", FIGURE }, + {"FOOTER", FOOTER }, + {"FORM", FORM }, + {"H1", H1 }, + {"H2", H2 }, + {"H3", H3 }, + {"H4", H4 }, + {"H5", H5 }, + {"H6", H6 }, + {"HEAD", HEAD }, + {"HEADER", HEADER }, + {"HGROUP", HGROUP }, + {"HTML", HTML }, + {"I", I }, + {"IFRAME", IFRAME }, + {"INS", INS }, + {"KBD", KBD }, + {"LABEL", LABEL }, + {"LEGEND", LEGEND }, + {"LI", LI }, + {"MAIN", MAIN }, + {"MAP", MAP }, + {"MARK", MARK }, + {"MATH", MATH }, + {"MENU", MENU }, + {"METER", METER }, + {"NAV", NAV }, + {"NOSCRIPT", NOSCRIPT }, + {"OBJECT", OBJECT }, + {"OL", OL }, + {"OPTGROUP", OPTGROUP }, + {"OPTION", OPTION }, + {"OUTPUT", OUTPUT }, + {"P", P }, + {"PICTURE", PICTURE }, + {"PRE", PRE }, + {"PROGRESS", PROGRESS }, + {"Q", Q }, + {"RB", RB }, + {"RP", RP }, + {"RT", RT }, + {"RTC", RTC }, + {"RUBY", RUBY }, + {"S", S }, + {"SAMP", SAMP }, + {"SCRIPT", SCRIPT }, + {"SECTION", SECTION }, + {"SELECT", SELECT }, + {"SLOT", SLOT }, + {"SMALL", SMALL }, + {"SPAN", SPAN }, + {"STRONG", STRONG }, + {"STYLE", STYLE }, + {"SUB", SUB }, + {"SUMMARY", SUMMARY }, + {"SUP", SUP }, + {"SVG", SVG }, + {"TABLE", TABLE }, + {"TBODY", TBODY }, + {"TD", TD }, + {"TEMPLATE", TEMPLATE }, + {"TEXTAREA", TEXTAREA }, + {"TFOOT", TFOOT }, + {"TH", TH }, + {"THEAD", THEAD }, + {"TIME", TIME }, + {"TITLE", TITLE }, + {"TR", TR }, + {"U", U }, + {"UL", UL }, + {"VAR", VAR }, + {"VIDEO", VIDEO }, + {"CUSTOM", CUSTOM }, +}; + +static const TagType TAG_TYPES_NOT_ALLOWED_IN_PARAGRAPHS[] = { + ADDRESS, ARTICLE, ASIDE, BLOCKQUOTE, DETAILS, DIV, DL, + FIELDSET, FIGCAPTION, FIGURE, FOOTER, FORM, H1, H2, + H3, H4, H5, H6, HEADER, HR, MAIN, + NAV, OL, P, PRE, SECTION, +}; + +static TagType tag_type_for_name(const String *tag_name) { + for (int i = 0; i < 126; i++) { + const TagMapEntry *entry = &TAG_TYPES_BY_TAG_NAME[i]; + if ( + strlen(entry->tag_name) == tag_name->size && + memcmp(tag_name->contents, entry->tag_name, tag_name->size) == 0 + ) { + return entry->tag_type; + } + } + return CUSTOM; +} + +static inline Tag tag_new() { + Tag tag; + tag.type = END_; + tag.custom_tag_name = (String) array_new(); + return tag; +} + +static inline Tag tag_for_name(String name) { + Tag tag = tag_new(); + tag.type = tag_type_for_name(&name); + if (tag.type == CUSTOM) { + tag.custom_tag_name = name; + } else { + array_delete(&name); + } + return tag; +} + +static inline void tag_free(Tag *tag) { + if (tag->type == CUSTOM) { + array_delete(&tag->custom_tag_name); + } +} + +static inline bool tag_is_void(const Tag *self) { + return self->type < END_OF_VOID_TAGS; +} + +static inline bool tag_eq(const Tag *self, const Tag *other) { + if (self->type != other->type) return false; + if (self->type == CUSTOM) { + if (self->custom_tag_name.size != other->custom_tag_name.size) { + return false; + } + if (memcmp( + self->custom_tag_name.contents, + other->custom_tag_name.contents, + self->custom_tag_name.size + ) != 0) { + return false; + } + } + return true; +} + +static bool tag_can_contain(Tag *self, const Tag *other) { + return true; + TagType child = other->type; + + switch (self->type) { + case LI: + return child != LI; + + case DT: + case DD: + return child != DT && child != DD; + + case P: + for (int i = 0; i < 26; i++) { + if (child == TAG_TYPES_NOT_ALLOWED_IN_PARAGRAPHS[i]) { + return false; + } + } + return true; + + case COLGROUP: + return child == COL; + + case RB: + case RT: + case RP: + return child != RB && child != RT && child != RP; + + case OPTGROUP: + return child != OPTGROUP; + + case TR: + return child != TR; + + case TD: + case TH: + return child != TD && child != TH && child != TR; + + default: + return true; + } +} diff --git a/internal/cbm/vendored/grammars/cfscript/tree_sitter/alloc.h b/internal/cbm/vendored/grammars/cfscript/tree_sitter/alloc.h new file mode 100644 index 000000000..1abdd1201 --- /dev/null +++ b/internal/cbm/vendored/grammars/cfscript/tree_sitter/alloc.h @@ -0,0 +1,54 @@ +#ifndef TREE_SITTER_ALLOC_H_ +#define TREE_SITTER_ALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +// Allow clients to override allocation functions +#ifdef TREE_SITTER_REUSE_ALLOCATOR + +extern void *(*ts_current_malloc)(size_t size); +extern void *(*ts_current_calloc)(size_t count, size_t size); +extern void *(*ts_current_realloc)(void *ptr, size_t size); +extern void (*ts_current_free)(void *ptr); + +#ifndef ts_malloc +#define ts_malloc ts_current_malloc +#endif +#ifndef ts_calloc +#define ts_calloc ts_current_calloc +#endif +#ifndef ts_realloc +#define ts_realloc ts_current_realloc +#endif +#ifndef ts_free +#define ts_free ts_current_free +#endif + +#else + +#ifndef ts_malloc +#define ts_malloc malloc +#endif +#ifndef ts_calloc +#define ts_calloc calloc +#endif +#ifndef ts_realloc +#define ts_realloc realloc +#endif +#ifndef ts_free +#define ts_free free +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ALLOC_H_ diff --git a/internal/cbm/vendored/grammars/cfscript/tree_sitter/array.h b/internal/cbm/vendored/grammars/cfscript/tree_sitter/array.h new file mode 100644 index 000000000..56fc8cd47 --- /dev/null +++ b/internal/cbm/vendored/grammars/cfscript/tree_sitter/array.h @@ -0,0 +1,330 @@ +#ifndef TREE_SITTER_ARRAY_H_ +#define TREE_SITTER_ARRAY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./alloc.h" + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4101) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#define Array(T) \ + struct { \ + T *contents; \ + uint32_t size; \ + uint32_t capacity; \ + } + +/// Initialize an array. +#define array_init(self) \ + ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) + +/// Create an empty array. +#define array_new() \ + { NULL, 0, 0 } + +/// Get a pointer to the element at a given `index` in the array. +#define array_get(self, _index) \ + (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) + +/// Get a pointer to the first element in the array. +#define array_front(self) array_get(self, 0) + +/// Get a pointer to the last element in the array. +#define array_back(self) array_get(self, (self)->size - 1) + +/// Clear the array, setting its size to zero. Note that this does not free any +/// memory allocated for the array's contents. +#define array_clear(self) ((self)->size = 0) + +/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is +/// less than the array's current capacity, this function has no effect. +#define array_reserve(self, new_capacity) \ + ((self)->contents = _array__reserve( \ + (void *)(self)->contents, &(self)->capacity, \ + array_elem_size(self), new_capacity) \ + ) + +/// Free any memory allocated for this array. Note that this does not free any +/// memory allocated for the array's contents. +#define array_delete(self) \ + do { \ + if ((self)->contents) ts_free((self)->contents); \ + (self)->contents = NULL; \ + (self)->size = 0; \ + (self)->capacity = 0; \ + } while (0) + +/// Push a new `element` onto the end of the array. +#define array_push(self, element) \ + do { \ + (self)->contents = _array__grow( \ + (void *)(self)->contents, (self)->size, &(self)->capacity, \ + 1, array_elem_size(self) \ + ); \ + (self)->contents[(self)->size++] = (element); \ + } while(0) + +/// Increase the array's size by `count` elements. +/// New elements are zero-initialized. +#define array_grow_by(self, count) \ + do { \ + if ((count) == 0) break; \ + (self)->contents = _array__grow( \ + (self)->contents, (self)->size, &(self)->capacity, \ + count, array_elem_size(self) \ + ); \ + memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ + (self)->size += (count); \ + } while (0) + +/// Append all elements from one array to the end of another. +#define array_push_all(self, other) \ + array_extend((self), (other)->size, (other)->contents) + +/// Append `count` elements to the end of the array, reading their values from the +/// `contents` pointer. +#define array_extend(self, count, other_contents) \ + (self)->contents = _array__splice( \ + (void*)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), (self)->size, 0, count, other_contents \ + ) + +/// Remove `old_count` elements from the array starting at the given `index`. At +/// the same index, insert `new_count` new elements, reading their values from the +/// `new_contents` pointer. +#define array_splice(self, _index, old_count, new_count, new_contents) \ + (self)->contents = _array__splice( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), _index, old_count, new_count, new_contents \ + ) + +/// Insert one `element` into the array at the given `index`. +#define array_insert(self, _index, element) \ + (self)->contents = _array__splice( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), _index, 0, 1, &(element) \ + ) + +/// Remove one element from the array at the given `index`. +#define array_erase(self, _index) \ + _array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index) + +/// Pop the last element off the array, returning the element by value. +#define array_pop(self) ((self)->contents[--(self)->size]) + +/// Assign the contents of one array to another, reallocating if necessary. +#define array_assign(self, other) \ + (self)->contents = _array__assign( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + (const void *)(other)->contents, (other)->size, array_elem_size(self) \ + ) + +/// Swap one array with another +#define array_swap(self, other) \ + do { \ + void *_array_swap_tmp = (void *)(self)->contents; \ + (self)->contents = (other)->contents; \ + (other)->contents = _array_swap_tmp; \ + _array__swap(&(self)->size, &(self)->capacity, \ + &(other)->size, &(other)->capacity); \ + } while (0) + +/// Get the size of the array contents +#define array_elem_size(self) (sizeof *(self)->contents) + +/// Search a sorted array for a given `needle` value, using the given `compare` +/// callback to determine the order. +/// +/// If an existing element is found to be equal to `needle`, then the `index` +/// out-parameter is set to the existing value's index, and the `exists` +/// out-parameter is set to true. Otherwise, `index` is set to an index where +/// `needle` should be inserted in order to preserve the sorting, and `exists` +/// is set to false. +#define array_search_sorted_with(self, compare, needle, _index, _exists) \ + _array__search_sorted(self, 0, compare, , needle, _index, _exists) + +/// Search a sorted array for a given `needle` value, using integer comparisons +/// of a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_with`. +#define array_search_sorted_by(self, field, needle, _index, _exists) \ + _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) + +/// Insert a given `value` into a sorted array, using the given `compare` +/// callback to determine the order. +#define array_insert_sorted_with(self, compare, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +/// Insert a given `value` into a sorted array, using integer comparisons of +/// a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_by`. +#define array_insert_sorted_by(self, field, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +// Private + +// Pointers to individual `Array` fields (rather than the entire `Array` itself) +// are passed to the various `_array__*` functions below to address strict aliasing +// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`. +// +// The `Array` type itself was not altered as a solution in order to avoid breakage +// with existing consumers (in particular, parsers with external scanners). + +/// This is not what you're looking for, see `array_erase`. +static inline void _array__erase(void* self_contents, uint32_t *size, + size_t element_size, uint32_t index) { + assert(index < *size); + char *contents = (char *)self_contents; + memmove(contents + index * element_size, contents + (index + 1) * element_size, + (*size - index - 1) * element_size); + (*size)--; +} + +/// This is not what you're looking for, see `array_reserve`. +static inline void *_array__reserve(void *contents, uint32_t *capacity, + size_t element_size, uint32_t new_capacity) { + void *new_contents = contents; + if (new_capacity > *capacity) { + if (contents) { + new_contents = ts_realloc(contents, new_capacity * element_size); + } else { + new_contents = ts_malloc(new_capacity * element_size); + } + *capacity = new_capacity; + } + return new_contents; +} + +/// This is not what you're looking for, see `array_assign`. +static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity, + const void *other_contents, uint32_t other_size, size_t element_size) { + void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size); + *self_size = other_size; + memcpy(new_contents, other_contents, *self_size * element_size); + return new_contents; +} + +/// This is not what you're looking for, see `array_swap`. +static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity, + uint32_t *other_size, uint32_t *other_capacity) { + uint32_t tmp_size = *self_size; + uint32_t tmp_capacity = *self_capacity; + *self_size = *other_size; + *self_capacity = *other_capacity; + *other_size = tmp_size; + *other_capacity = tmp_capacity; +} + +/// This is not what you're looking for, see `array_push` or `array_grow_by`. +static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity, + uint32_t count, size_t element_size) { + void *new_contents = contents; + uint32_t new_size = size + count; + if (new_size > *capacity) { + uint32_t new_capacity = *capacity * 2; + if (new_capacity < 8) new_capacity = 8; + if (new_capacity < new_size) new_capacity = new_size; + new_contents = _array__reserve(contents, capacity, element_size, new_capacity); + } + return new_contents; +} + +/// This is not what you're looking for, see `array_splice`. +static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity, + size_t element_size, + uint32_t index, uint32_t old_count, + uint32_t new_count, const void *elements) { + uint32_t new_size = *size + new_count - old_count; + uint32_t old_end = index + old_count; + uint32_t new_end = index + new_count; + assert(old_end <= *size); + + void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size); + + char *contents = (char *)new_contents; + if (*size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (*size - old_end) * element_size + ); + } + if (new_count > 0) { + if (elements) { + memcpy( + (contents + index * element_size), + elements, + new_count * element_size + ); + } else { + memset( + (contents + index * element_size), + 0, + new_count * element_size + ); + } + } + *size += new_count - old_count; + + return new_contents; +} + +/// A binary search routine, based on Rust's `std::slice::binary_search_by`. +/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. +#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ + do { \ + *(_index) = start; \ + *(_exists) = false; \ + uint32_t size = (self)->size - *(_index); \ + if (size == 0) break; \ + int comparison; \ + while (size > 1) { \ + uint32_t half_size = size / 2; \ + uint32_t mid_index = *(_index) + half_size; \ + comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ + if (comparison <= 0) *(_index) = mid_index; \ + size -= half_size; \ + } \ + comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ + if (comparison == 0) *(_exists) = true; \ + else if (comparison < 0) *(_index) += 1; \ + } while (0) + +/// Helper macro for the `_sorted_by` routines below. This takes the left (existing) +/// parameter by reference in order to work with the generic sorting function above. +#define _compare_int(a, b) ((int)*(a) - (int)(b)) + +#ifdef _MSC_VER +#pragma warning(pop) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ARRAY_H_ diff --git a/internal/cbm/vendored/grammars/cfscript/tree_sitter/parser.h b/internal/cbm/vendored/grammars/cfscript/tree_sitter/parser.h new file mode 100644 index 000000000..858107deb --- /dev/null +++ b/internal/cbm/vendored/grammars/cfscript/tree_sitter/parser.h @@ -0,0 +1,286 @@ +#ifndef TREE_SITTER_PARSER_H_ +#define TREE_SITTER_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define ts_builtin_sym_error ((TSSymbol)-1) +#define ts_builtin_sym_end 0 +#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 + +#ifndef TREE_SITTER_API_H_ +typedef uint16_t TSStateId; +typedef uint16_t TSSymbol; +typedef uint16_t TSFieldId; +typedef struct TSLanguage TSLanguage; +typedef struct TSLanguageMetadata { + uint8_t major_version; + uint8_t minor_version; + uint8_t patch_version; +} TSLanguageMetadata; +#endif + +typedef struct { + TSFieldId field_id; + uint8_t child_index; + bool inherited; +} TSFieldMapEntry; + +// Used to index the field and supertype maps. +typedef struct { + uint16_t index; + uint16_t length; +} TSMapSlice; + +typedef struct { + bool visible; + bool named; + bool supertype; +} TSSymbolMetadata; + +typedef struct TSLexer TSLexer; + +struct TSLexer { + int32_t lookahead; + TSSymbol result_symbol; + void (*advance)(TSLexer *, bool); + void (*mark_end)(TSLexer *); + uint32_t (*get_column)(TSLexer *); + bool (*is_at_included_range_start)(const TSLexer *); + bool (*eof)(const TSLexer *); + void (*log)(const TSLexer *, const char *, ...); +}; + +typedef enum { + TSParseActionTypeShift, + TSParseActionTypeReduce, + TSParseActionTypeAccept, + TSParseActionTypeRecover, +} TSParseActionType; + +typedef union { + struct { + uint8_t type; + TSStateId state; + bool extra; + bool repetition; + } shift; + struct { + uint8_t type; + uint8_t child_count; + TSSymbol symbol; + int16_t dynamic_precedence; + uint16_t production_id; + } reduce; + uint8_t type; +} TSParseAction; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; +} TSLexMode; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; + uint16_t reserved_word_set_id; +} TSLexerMode; + +typedef union { + TSParseAction action; + struct { + uint8_t count; + bool reusable; + } entry; +} TSParseActionEntry; + +typedef struct { + int32_t start; + int32_t end; +} TSCharacterRange; + +struct TSLanguage { + uint32_t abi_version; + uint32_t symbol_count; + uint32_t alias_count; + uint32_t token_count; + uint32_t external_token_count; + uint32_t state_count; + uint32_t large_state_count; + uint32_t production_id_count; + uint32_t field_count; + uint16_t max_alias_sequence_length; + const uint16_t *parse_table; + const uint16_t *small_parse_table; + const uint32_t *small_parse_table_map; + const TSParseActionEntry *parse_actions; + const char * const *symbol_names; + const char * const *field_names; + const TSMapSlice *field_map_slices; + const TSFieldMapEntry *field_map_entries; + const TSSymbolMetadata *symbol_metadata; + const TSSymbol *public_symbol_map; + const uint16_t *alias_map; + const TSSymbol *alias_sequences; + const TSLexerMode *lex_modes; + bool (*lex_fn)(TSLexer *, TSStateId); + bool (*keyword_lex_fn)(TSLexer *, TSStateId); + TSSymbol keyword_capture_token; + struct { + const bool *states; + const TSSymbol *symbol_map; + void *(*create)(void); + void (*destroy)(void *); + bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); + unsigned (*serialize)(void *, char *); + void (*deserialize)(void *, const char *, unsigned); + } external_scanner; + const TSStateId *primary_state_ids; + const char *name; + const TSSymbol *reserved_words; + uint16_t max_reserved_word_set_size; + uint32_t supertype_count; + const TSSymbol *supertype_symbols; + const TSMapSlice *supertype_map_slices; + const TSSymbol *supertype_map_entries; + TSLanguageMetadata metadata; +}; + +static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) { + uint32_t index = 0; + uint32_t size = len - index; + while (size > 1) { + uint32_t half_size = size / 2; + uint32_t mid_index = index + half_size; + const TSCharacterRange *range = &ranges[mid_index]; + if (lookahead >= range->start && lookahead <= range->end) { + return true; + } else if (lookahead > range->end) { + index = mid_index; + } + size -= half_size; + } + const TSCharacterRange *range = &ranges[index]; + return (lookahead >= range->start && lookahead <= range->end); +} + +/* + * Lexer Macros + */ + +#ifdef _MSC_VER +#define UNUSED __pragma(warning(suppress : 4101)) +#else +#define UNUSED __attribute__((unused)) +#endif + +#define START_LEXER() \ + bool result = false; \ + bool skip = false; \ + UNUSED \ + bool eof = false; \ + int32_t lookahead; \ + goto start; \ + next_state: \ + lexer->advance(lexer, skip); \ + start: \ + skip = false; \ + lookahead = lexer->lookahead; + +#define ADVANCE(state_value) \ + { \ + state = state_value; \ + goto next_state; \ + } + +#define ADVANCE_MAP(...) \ + { \ + static const uint16_t map[] = { __VA_ARGS__ }; \ + for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \ + if (map[i] == lookahead) { \ + state = map[i + 1]; \ + goto next_state; \ + } \ + } \ + } + +#define SKIP(state_value) \ + { \ + skip = true; \ + state = state_value; \ + goto next_state; \ + } + +#define ACCEPT_TOKEN(symbol_value) \ + result = true; \ + lexer->result_symbol = symbol_value; \ + lexer->mark_end(lexer); + +#define END_STATE() return result; + +/* + * Parse Table Macros + */ + +#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT) + +#define STATE(id) id + +#define ACTIONS(id) id + +#define SHIFT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = (state_value) \ + } \ + }} + +#define SHIFT_REPEAT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = (state_value), \ + .repetition = true \ + } \ + }} + +#define SHIFT_EXTRA() \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .extra = true \ + } \ + }} + +#define REDUCE(symbol_name, children, precedence, prod_id) \ + {{ \ + .reduce = { \ + .type = TSParseActionTypeReduce, \ + .symbol = symbol_name, \ + .child_count = children, \ + .dynamic_precedence = precedence, \ + .production_id = prod_id \ + }, \ + }} + +#define RECOVER() \ + {{ \ + .type = TSParseActionTypeRecover \ + }} + +#define ACCEPT_INPUT() \ + {{ \ + .type = TSParseActionTypeAccept \ + }} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_PARSER_H_ diff --git a/src/discover/language.c b/src/discover/language.c index 03b0b7bc6..7df8f218f 100644 --- a/src/discover/language.c +++ b/src/discover/language.c @@ -433,6 +433,10 @@ static const ext_entry_t EXT_TABLE[] = { /* Qt QML */ {".qml", CBM_LANG_QML}, + /* CFML / ColdFusion — .cfc components are script-dialect; .cfm are tag templates */ + {".cfc", CBM_LANG_CFSCRIPT}, + {".cfm", CBM_LANG_CFML}, + /* Mermaid */ {".mermaid", CBM_LANG_MERMAID}, @@ -753,6 +757,8 @@ static const char *LANG_NAMES[CBM_LANG_COUNT] = { [CBM_LANG_PONY] = "Pony", [CBM_LANG_LUAU] = "Luau", [CBM_LANG_QML] = "QML", + [CBM_LANG_CFSCRIPT] = "CFML", + [CBM_LANG_CFML] = "CFML", [CBM_LANG_JANET] = "Janet", [CBM_LANG_SWAY] = "Sway", [CBM_LANG_NASM] = "NASM", diff --git a/tests/test_extraction.c b/tests/test_extraction.c index d450ae4ef..0250edbdc 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -243,6 +243,40 @@ TEST(extract_qml_issue42) { PASS(); } +/* --- CFML script dialect: .cfc components (Lucee/ColdFusion, #38) --- */ +TEST(extract_cfscript_issue38) { + CBMFileResult *r = extract("component {\n" + " public function getUser(numeric id) {\n" + " return loadUser(id);\n" + " }\n" + " function loadUser(id) {\n" + " return id * 2;\n" + " }\n" + "}\n", + CBM_LANG_CFSCRIPT, "app", "User.cfc"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(count_defs_with_label(r, "Function") >= 2); + ASSERT(has_def(r, "Function", "getUser")); + ASSERT(has_def(r, "Function", "loadUser")); + ASSERT(has_call(r, "loadUser")); + cbm_free_result(r); + PASS(); +} + +/* --- CFML tag dialect: .cfm templates with (#38) --- */ +TEST(extract_cfml_tag_issue38) { + CBMFileResult *r = extract("\n" + " \n" + " \n" + "\n", + CBM_LANG_CFML, "app", "index.cfm"); + ASSERT_NOT_NULL(r); + ASSERT(has_def(r, "Function", "greet")); + cbm_free_result(r); + PASS(); +} + /* --- Java --- */ TEST(java_class) { CBMFileResult *r = extract( @@ -2518,6 +2552,8 @@ SUITE(extraction) { RUN_TEST(extract_powershell_issue35); RUN_TEST(extract_luau_issue39); RUN_TEST(extract_qml_issue42); + RUN_TEST(extract_cfscript_issue38); + RUN_TEST(extract_cfml_tag_issue38); /* OOP */ RUN_TEST(java_class); diff --git a/tests/test_language.c b/tests/test_language.c index 74ec99eaa..23eb25ee6 100644 --- a/tests/test_language.c +++ b/tests/test_language.c @@ -753,6 +753,12 @@ TEST(lang_ext_qml) { PASS(); } +TEST(lang_ext_cfml) { + ASSERT_EQ(cbm_language_for_extension(".cfc"), CBM_LANG_CFSCRIPT); + ASSERT_EQ(cbm_language_for_extension(".cfm"), CBM_LANG_CFML); + PASS(); +} + TEST(lang_ext_janet) { ASSERT_EQ(cbm_language_for_extension(".janet"), CBM_LANG_JANET); PASS(); @@ -1222,6 +1228,7 @@ SUITE(language) { RUN_TEST(lang_ext_pony); RUN_TEST(lang_ext_luau); RUN_TEST(lang_ext_qml); + RUN_TEST(lang_ext_cfml); RUN_TEST(lang_ext_janet); RUN_TEST(lang_ext_sway); RUN_TEST(lang_ext_nasm);