|
11 | 11 | #include "cbm.h" // CBMLanguage, CBM_LANG_* |
12 | 12 |
|
13 | 13 | #include "foundation/constants.h" |
| 14 | +#include "foundation/compat.h" // cbm_strcasestr |
14 | 15 | #include "foundation/compat_fs.h" |
15 | 16 |
|
16 | 17 | enum { LANG_SCAN_PASSES = 2 }; |
@@ -453,7 +454,10 @@ static const ext_entry_t EXT_TABLE[] = { |
453 | 454 | /* Qt QML */ |
454 | 455 | {".qml", CBM_LANG_QML}, |
455 | 456 |
|
456 | | - /* CFML / ColdFusion — .cfc components are script-dialect; .cfm are tag templates */ |
| 457 | + /* CFML / ColdFusion — .cfm are tag templates; .cfc components may be EITHER |
| 458 | + * script-dialect (component { ... }) or tag-dialect (<cfcomponent> ...). The |
| 459 | + * table default is script; tag-based .cfc are resolved by content in |
| 460 | + * cbm_disambiguate_cfc(). */ |
457 | 461 | {".cfc", CBM_LANG_CFSCRIPT}, |
458 | 462 | {".cfm", CBM_LANG_CFML}, |
459 | 463 |
|
@@ -1122,3 +1126,74 @@ CBMLanguage cbm_disambiguate_inc(const char *path) { |
1122 | 1126 | } |
1123 | 1127 | return CBM_LANG_BITBAKE; |
1124 | 1128 | } |
| 1129 | + |
| 1130 | +/* Case-insensitive prefix match (portable — no strncasecmp dependency). */ |
| 1131 | +static bool starts_with_ci(const char *s, const char *prefix) { |
| 1132 | + for (; *prefix; s++, prefix++) { |
| 1133 | + if (tolower((unsigned char)*s) != tolower((unsigned char)*prefix)) { |
| 1134 | + return false; |
| 1135 | + } |
| 1136 | + } |
| 1137 | + return true; |
| 1138 | +} |
| 1139 | + |
| 1140 | +/* Disambiguate .cfc files: a ColdFusion component may be written in the script |
| 1141 | + * dialect ("component { ... }", parsed by the JS-like cfscript grammar) or the |
| 1142 | + * tag dialect ("<cfcomponent> ... <cffunction>", parsed by the HTML-derived cfml |
| 1143 | + * grammar). The extension table defaults to cfscript because that is what modern |
| 1144 | + * Lucee/ACF templates use, but large legacy codebases are predominantly tag-based |
| 1145 | + * and feeding those to the wrong grammar fails wholesale. Routing rules: |
| 1146 | + * 1. A "<cfcomponent" or top-level "<cffunction" tag ⇒ tag dialect. (The latter |
| 1147 | + * catches "bare" tag components that omit the <cfcomponent> wrapper.) This |
| 1148 | + * wins regardless of any leading <!---/<cfscript>, so it is checked first. |
| 1149 | + * 2. Otherwise the file is script-dialect content. Find the first significant |
| 1150 | + * token, skipping whitespace and <!--- ---> comments: |
| 1151 | + * - a leading "<cfscript>" wrapper is still script content ⇒ cfscript; |
| 1152 | + * - a different leading tag (e.g. <cfquery> in a bare-tag file) ⇒ cfml; |
| 1153 | + * - anything else ("component { ... }") ⇒ cfscript. |
| 1154 | + * Defaults to CBM_LANG_CFSCRIPT on any doubt (preserves table behaviour). */ |
| 1155 | +CBMLanguage cbm_disambiguate_cfc(const char *path) { |
| 1156 | + if (!path) { |
| 1157 | + return CBM_LANG_CFSCRIPT; |
| 1158 | + } |
| 1159 | + |
| 1160 | + FILE *f = cbm_fopen(path, "r"); |
| 1161 | + if (!f) { |
| 1162 | + return CBM_LANG_CFSCRIPT; |
| 1163 | + } |
| 1164 | + |
| 1165 | + /* Read a generous head: tag components can carry a large license/revision |
| 1166 | + * comment block before the <cfcomponent> opener. */ |
| 1167 | + char buf[CBM_SZ_16K + SKIP_ONE]; |
| 1168 | + size_t n = fread(buf, SKIP_ONE, CBM_SZ_16K, f); |
| 1169 | + buf[n] = '\0'; |
| 1170 | + (void)fclose(f); |
| 1171 | + |
| 1172 | + /* Rule 1: explicit tag-component markers ⇒ tag dialect. */ |
| 1173 | + if (cbm_strcasestr(buf, "<cfcomponent") != NULL || cbm_strcasestr(buf, "<cffunction") != NULL) { |
| 1174 | + return CBM_LANG_CFML; |
| 1175 | + } |
| 1176 | + |
| 1177 | + /* Rule 2: locate the first significant token, past whitespace and comments. */ |
| 1178 | + const char *p = buf; |
| 1179 | + for (;;) { |
| 1180 | + while (*p && isspace((unsigned char)*p)) { |
| 1181 | + p++; |
| 1182 | + } |
| 1183 | + if (starts_with_ci(p, "<!---")) { |
| 1184 | + const char *end = strstr(p + SLEN("<!---"), "--->"); |
| 1185 | + if (!end) { |
| 1186 | + break; /* comment runs past the buffer — treat as no token */ |
| 1187 | + } |
| 1188 | + p = end + SLEN("--->"); |
| 1189 | + continue; |
| 1190 | + } |
| 1191 | + break; |
| 1192 | + } |
| 1193 | + if (*p == '<') { |
| 1194 | + /* A leading <cfscript> wrapper is script content; any other leading tag |
| 1195 | + * (bare-tag file) is tag content. */ |
| 1196 | + return starts_with_ci(p, "<cfscript") ? CBM_LANG_CFSCRIPT : CBM_LANG_CFML; |
| 1197 | + } |
| 1198 | + return CBM_LANG_CFSCRIPT; |
| 1199 | +} |
0 commit comments