Analyzer::Cfml::CfmlEngine
Inherits Analyzer < FileHelper < Reference < Object
Shared CFML parsing layer.
See AGENTS.md §"Analyzer Layering": this owns file selection and the syntax handling every CFML adapter needs, so framework analyzers (Taffy, ColdBox, Wheels, FW/1) consume components and functions rather than re-implementing tag/script parsing.
The two syntaxes are the whole problem. CFML is written either as
tags (<cffunction name="x" access="remote">) or as cfscript
(remote string function x()), the two mix inside a single file, and
everything is case-insensitive.
Constants
Tags may span lines and pad their = for alignment, so attributes
are matched with \s*=\s* and tag bodies with [\s\S]*?.
Verbs a CFML framework can register. Route DSLs take verbs as free text, so a value outside this set is not turned into a method.
A component header is a handful of attributes; bound the scan so a file with no opening brace cannot walk the whole content.
component ... { header in script syntax, and a function
declaration with its trailing attribute list up to the body brace.
Rules::JAVA with Empties::DropAll and, like erlang/cowboy.cr and
php/wordpress.cr, WITHOUT clamping: the body this replaces closed
brackets with a bare depth -= 1, so an argument slice that opens on
a closer drives depth negative and suppresses every later split.
Observable on the unbalanced fragments the CFML regexes hand over, so
it is preserved rather than normalised.
Escape::None because CFML has no backslash escape at all. "C:\log\"
is the seven-character string C:\log\, and a quote is escaped by
DOUBLING it ("say ""hi"""). Reading \ as an escape — which the
hand-rolled body this replaced did, and which the rest of this engine
did too until the same change — swallowed the closing quote of any
string ending in a backslash, i.e. any Windows path, leaving the run
open so the remainder of the argument list merged into the current part
and every argument after it was lost.
There is deliberately no doubled-quote mode for the OTHER half of the
rule, and that is not an oversight. For a top-level splitter "a
repeated delimiter is a literal delimiter" and "quotes toggle" are
indistinguishable: a doubled pair is two ADJACENT quote characters, so
the toggling model's spurious closed-then-reopened window is zero
characters wide. The state after any maximal run of quotes is the same
under both models (a run of length L flips the state iff L is odd
either way), and the only characters ever processed in a differing
state are the quote characters themselves, which neither nest nor
split. Checked by brute force rather than by argument: a doubling
model run against TopLevelSplit over all 1,440 Rules combinations
x 111,111 inputs (every string up to length 5 over " ' , a \ ( ) <
backtick {) agreed on all 159,999,840 comparisons. matching_delimiter
below implements doubling explicitly; that is equally correct and
equally immaterial, and it is kept because a byte scanner reads more
obviously with the rule written out.
File-local because this is the only splitter pairing clamp: false
with DropAll.
Attribute names carry : and - in the wild (taffy:uri,
data-required), and values use either quote style.
TestBox names suites <Something>Test.cfc / <Something>Spec.cfc.
The leading .+ is load-bearing: a component named exactly
Test.cfc is a demo, not a suite (fw1 ships one that declares a
real remote method), and an anchored ends_with? swallowed it.