Documentation note (2026-09-19): Implemented the dedicated Reaper process (run_reaper_process) and supervisor lifecycle management in src/reaper.cpp and src/appserver.cpp. The Reaper process runs in an isolated child process using Linux timerfd (armed with store.earliestExpiry using TFD_TIMER_ABSTIME) and signalfd for immediate unblocking on SIGTERM/SIGINT. The supervisor in appserver.cpp forks and monitors the Reaper via SIGCHLD, automatically respawning it if it ever exits unexpectedly, and signals clean termination on server shutdown (SIGINT/SIGTERM) with PR_SET_PDEATHSIG protection. Covered by ScopeStoreTest.ReaperProcessForkTimerAndSignalWakeup.
Documentation note (2026-09-19): Implemented the Reaper cycle processor (reap_expired_scopes) in include/webstrada/reaper.h and src/reaper.cpp. When expired records are detected, store.claimExpired atomically copies scope data into memory and deletes the rows within a single SQLite transaction, releasing database locks immediately before CFML code runs. The reaper then deserializes SessionScope and ApplicationScope in memory and executes onSessionEnd(SessionScope, ApplicationScope) and onApplicationEnd(ApplicationScope) via Application.cfc (associated via cfc_path in cf_scope). Added unit test ScopeStoreTest.ReapExpiredScopesInMemLifecycle verifying in-memory callback execution, scope argument passing, and zero-lock execution.
Documentation note (2026-09-19): Implemented ColdFusion background lifecycle invocation helpers (cf_invoke_on_session_end, cf_invoke_on_application_end, and their *_with_info variants) in src/core/core_component.cpp and include/webstrada/cf8.h. Inside onSessionEnd(SessionScope, ApplicationScope) and onApplicationEnd(ApplicationScope), parameters are passed strictly as explicit arguments (arguments.SessionScope, arguments.ApplicationScope), while implicit request scopes (CGI, URL, FORM) and built-in scopes are unbound. All standard output generated within these callbacks is silently discarded, and unhandled exceptions are caught and logged without aborting caller execution or background workers. Wired sessionInvalidate() to invoke onSessionEnd synchronously before evicting the session, fully verified against Adobe ColdFusion 2025 (tests/cfm/session_end_test/test.cfm, tests/cfm/session_end_edge_test/test.cfm) and covered by WorkerTest.ScopeSessionInvalidateInvokesOnSessionEnd.
Documentation note (2026-09-19): Updated application and session scope expiration handling across worker and ScopeStore. Session and application accesses now perform atomic lock-free touchSession / touchApplication checking against expires_at in SQLite WAL store (touchRow). On expiration, stale live in-memory sessions are evicted and fresh sessions/cookies minted. Added unit tests for session expiration and fresh token generation (WorkerTest.ScopeSessionExpiresAndMintsFreshSession).
Documentation note (2026-09-19): Documented WebStrada's intra-request execution tracking and profiling system in README.md (enabled and managed in the Admin Panel). The profiler tracks granular timing across template execution, Application.cfc lifecycle callbacks (onRequestStart, onRequestEnd), database queries, custom tags, and CFC instantiations/method invocations, as well as line-level execution tracing persisted in SQLite.
Compatibility note (2026-09-08): the ColdFusion 2025 feature-gap audit is summarized in README.md and technically decomposed in TODO.md. The repository's existing tag/function tables remain the source of truth for exact unsupported names. The audit additionally records pending 2025 language features (??, ?., =>, spread, destructuring, multi-assignment, dynamic properties, QoQ enhancements, and cacheMaxIdleTime) and ColdFusion 2025 Update 8 capabilities (AI/LLM, MCP, RAG/vector stores, passkeys/Argon2, Sets, and async APIs).
Compatibility note (2026-09-08): the elevated host full unit suite currently passes 1,432/1,478 tests; 12 host-level failures remain documented in BUGS.md. The formerly failing ComponentTest.ScriptFormConstructorCallsAndWriteOutput and the obsolete CreateGUID assertion now pass; the sandbox adds one environment-only log-path failure.
Compatibility note (2026-09-08): <cferror> request and exception handlers now populate Browser, RemoteAddress, HTTPReferer, and QueryString from CGI request fields; both diagnostics paths include the captured template/line location; and Suppressed renders with CF's Java Throwable-array form. Covered by CferrorTest.RequestHandlerIncludesLocationAndRequestMetadata and CferrorTest.ExceptionErrorStructIncludesRequestMetadataAndCfSuppressedForm.
Compatibility note (2026-09-08): <cferror> error.stackTrace and error.rootCause.stackTrace now expose the captured CFML call stack as innermost-first path:line[:FUNCTION] entries. This is intentionally CFML runtime information, not a Java stack trace; covered by CferrorTest.ExceptionErrorStructIncludesCfmlStackTrace.
Compatibility note (2026-09-07): IsProtected, IsAuthenticated, and IsAuthorized are obsolete ColdFusion security functions (removed in CF MX / CF 6). In modern Adobe ColdFusion (CF 2021/2025), calling them throws Variable <NAME> is undefined. (verified on the RDS host) and UDFs with these names are allowed. Implemented in fn_isprotected.cpp, fn_isauthenticated.cpp, and fn_isauthorized.cpp reproducing the exact CF 2025 error. Byte-verified against Adobe ColdFusion 2025 (tests/cfm/is_protected_authenticated_authorized.cfm and JitExpressionTest.Tier2Is*).
Compatibility note (2026-09-07): LambdaOperator (=>), safe navigation ObjectMember (?.), and null-coalescing TernaryOperator (??) are added to cfml_definition.json. The codegen stage throws unsupported operator when encountering any of these operators across expressions, statements, struct literals, function parameter declarations, and sharp interpolations. Covered by JitExpressionTest.UnsupportedOperatorsThrow.
Compatibility note (2026-09-07): CFScript keywords in cfml_definition.json updated with complete keyword set (var, function, new, this, super, null, try, catch, finally, if, then, else, switch, case, default, break, continue, while, do, for, in, return, throw, rethrow, retry, component, interface, property, pageencoding, import, include, param, lock, transaction, thread, public, private, remote, package, static, final, abstract, required). Supported keywords are hooked across statement and expression codegen (including null literal, switch/case/default, closures, member contexts, variable identifier positions, and tag attribute parsing). Unsupported script keywords (lock, transaction, thread, param, retry, component, interface, property) throw unsupported keyword in codegen when used as statements or expressions. Covered by JitExpressionTest.ScriptKeywordsSupportAndUnsupportedThrows.
Compatibility note (2026-09-07): SpreadOperator (...) is added to cfml_definition.json (inside Expression, ScriptExpression, Parenthesis, and SquareBrackets). The codegen stage now throws unimplemented operator when encountering the spread operator across expressions, struct/array literals, function parameter declarations, and sharp interpolations. Covered by JitExpressionTest.SpreadOperatorUnimplementedThrows.
Compatibility note (2026-09-07): IncDecOperator (++ / --) is now a single dedicated grammar token in cfml_definition.json, placed inside ExpressionTokens. Pre-increment (++x, --x) and post-increment (x++, x--) are fully supported in CFScript expressions: post-form returns the old value and then mutates the variable; pre-form mutates first and returns the new value. The engine already handled the two-adjacent-sign-token case; this stage wires up the dedicated single-token grammar so the textparser produces an IncDecOperator token instead of two AddOperator tokens. Byte-verified against Adobe ColdFusion 2025 (tests/cfm/incdec_operator_test.cfm; the original tests/cfm/cfscript_incr_test.cfm continues to pass).
Compatibility note (2026-09-08): CFScript while and do-while loops execute correctly when the grammar tokenizes their statement keywords as Keyword. The reproducer tests/cfm/cfscript_while_test.cfm emits 012|DO|134|W and matches Adobe ColdFusion 2025 exactly; focused JIT coverage is in JitExpressionTest.ScriptWhileAndDoWhile.
Build note (2026-09-08): Release optimization flags and link-time optimization
are configured centrally in CMakeLists.txt; build-release.sh now only selects
the Release configuration and builds it. LTO is always enabled for Release.
Both build scripts explicitly select GCC; Release links use GNU ld.bfd.
The redundant Release -O3 and unused shared-linker flag overrides were removed;
CMake's standard Release flags already provide -O3, and this project builds
static libraries and executables only.
Release size flags now enable per-function/data sections and linker garbage
collection; stripping remains enabled for the executable targets.
Get all technical info from: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tag-summary.html
Compatibility note (2026-09-02): the built executables are now all-lowercase:
the FastCGI server is bin/webstrada (was bin/WebStrada) and the CLI is
bin/webstrada-cli (was bin/WebStrada-cli; webstrada-tests was already
lowercase). CMake targets renamed accordingly (webstrada, webstrada-cli);
runtime references updated in Dockerfile, deploy/s6-rc.d/webstrada/run,
http-dev.py, tests/verify_with_coldfusion.py, tests/verify_draw_pixels.py,
README, and AGENTS build instructions. Log paths/names are lowercase too: the
default log directory is now /var/log/webstrada/ (was /var/log/WebStrada/)
and the CSV thread-name field is http-nio-webstrada-exec-1.
Compatibility note (2026-09-02): the Docker admin-builder stage upgrades npm
to 12.0.2 (npm install -g npm@12.0.2) so npm's "New major version of npm
available!" reminder no longer shows during npm ci/ng build. The bundled
update notifier is intentionally left enabled so the reminder returns when a
newer npm (e.g. 13.x) ships.
Compatibility note (2026-09-02): the Docker admin-builder stage now pins
FROM --platform=$ADMIN_PLATFORM so the Angular SPA is built natively on the
host platform instead of under QEMU. Cross-arch buildx builds previously died
with SIGILL/exit 132 (qemu: uncaught target signal 4) during
npm ci && npm run build on the emulated arm64 target. The output is a static
SPA, so it can be copied into any target arch unchanged. BUILDPLATFORM is a
buildx-only automatic arg that plain docker build force-blanks, so a custom
global ARG ADMIN_PLATFORM (default linux/amd64) is used; build_docker.sh
derives the host platform from docker info and passes it via --build-arg.
Compatibility note (2026-09-02): the system SQLite stores (ScopeStore,
CacheStore, ProfilerStore) are opened in SQLite shared-cache mode
(sqlite3_open_v2 with SQLITE_OPEN_SHAREDCACHE) with PRAGMA journal_mode=WAL
for multi-process access. The one-time WAL switch on a brand-new database is
retried with a bounded busy loop and busy_timeout=5000 is set before the
switch, because several prefork workers opening the database for the first time
at the same moment would otherwise hit PRAGMA journal_mode's database is locked (it does not honor busy_timeout) and a losing worker would silently
run the whole process without a scope/cache/profiler store. Cross-process
coordination remains SQLite's own WAL locking (SQLITE_OPEN_SHAREDCACHE only
shares the page cache between connections in the SAME process). Regression
coverage: CacheTest.ConcurrentFirstOpenFromMultipleProcesses.
Compatibility note (2026-08-27): <cfmail> is accepted as a non-delivering
stub that logs all evaluated attributes and consumes its body, including
nested <cfmailpart> content. No SMTP operation is performed.
Compatibility note (2026-08-27): <cfmailpart> and <cfmailparam> are also
accepted as non-delivering logging stubs; nested mail content is consumed
without page output or SMTP/file-attachment work.
Compatibility note (2026-08-27): Adobe script-form savecontent variable="..." { ... } and include "..."; statements are compiled as statements rather
than being sent to the expression parser. This fixes loading script CFCs such
as Mango's sampledash plugin; verified in tests/cfm/script_savecontent_test.cfm.
Compatibility note (2026-08-26): quoted string keys in named function
arguments (for example 'emailto' = '') are accepted by the JIT and
interpreter, matching Adobe ColdFusion. This fixes plugin CFC loading for
Mango_2_1; verified in tests/cfm/quoted_named_argument_test.cfm.
Compatibility note (2026-08-27): Mango_2_1 setup author creation now passes
password, name, email in the order required by AdminUtil.newAuthor, then
places the role in argument 7 and active=true in argument 8. The previous
setup call validated the author's name as an email and stopped with “Email is
not valid.”
Compatibility note (2026-08-26): typed Boolean UDF argument conversion now
reports Adobe ColdFusion's argument-validation message when a value cannot be
converted, including the uppercase argument name. Verified against CF 2025 in
tests/cfm/typed_boolean_argument_error_test.cfm.
Compatibility note (2026-08-26): indexed assignment through an undefined CFC
this member creates the member as a struct before descending into it. This
fixes MangoBlog setup's this.mappings["/org/mangoblog"] initialization.
Regression coverage: ComponentTest.IndexedAssignmentCreatesMissingComponentMember
and tests/cfm/component_implicit_member_test/.
Compatibility note (2026-08-26): script-form function declarations nested in
tag-based CFCs are registered as component methods, fixing MangoBlog's
unqualified structBlend() call from Mango.cfc.
Regression coverage: ComponentTest.TagComponentScriptFunctionIsComponentMethod
and tests/cfm/component_script_method_test/.
The tag-based CFC path also skips hoisted script declarations when compiling
the surrounding <cfscript> block as executable code. Access and return-type
modifiers such as private function and public string function are handled,
so each declaration is compiled once as a component method. Regression
coverage is in tests/cfm/component_script_private_method_test/.
Compatibility note (2026-08-26): dotted assignments in tag-based component
methods now flatten parser-glued names such as this.settings before walking
the live component this scope. Missing intermediate members are created as
structs and writes persist on the component instance, fixing MangoBlog's
Blog.cfc:setSettings() SETTINGS.SKINS.PATH failure. Regression coverage:
ComponentTest.TagComponentNestedThisAssignmentCreatesMembers and
tests/cfm/component_nested_this_assignment_test/.
Compatibility note (2026-08-26): cfscript new expressions now accept the
class-name Variable token emitted before the constructor call token, including
plain names such as new LocaleManager(...). This fixes MangoBlog's
internationalizer construction path. Existing coverage includes
ComponentTest.NewAutoCallsInit.
Compatibility note (2026-08-26): compound expressions in nested
<cfsavecontent>/<cfoutput> bodies now compile in the active component-method
context, preserving unqualified private CFC method calls. This fixes MangoBlog's
Statistics formatAge() call. Regression coverage:
ComponentTest.PrivateMethodInsideCfsavecontent and
tests/cfm/cfsavecontent_private_method_test.cfm.
Compatibility note (2026-08-26): active component/UDF local and arguments
scopes now take precedence over an included template's caller-local scope;
this fixes component calls from MangoBlog's request hook. Regression coverage:
ComponentTest.CfincludeComponentCallUsesCalleeArguments.
Compatibility note (2026-08-26): the same callee-local precedence applies when
a component/UDF is invoked from a custom tag. Unqualified assignments now reach
the active UDF's var/arguments scope before the custom tag's private scope;
this fixes MangoBlog's Cache.checkAndRetrieve() return-type failure at the
HTTP root URL. Regression coverage: ComponentTest.ComponentUdfLocalsWinOverCustomTagVariables
and tests/cfm/custom_tag_udf_scope_test.cfm.
Functions reference: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions.html
The per-worker TemplateCache checks source modification times for both .cfm
templates and .cfc component definitions. This covers ordinary components
and the nearest Application.cfm/Application.cfc; edited application files
are picked up on the next request, with a changed Application.cfc rebuilt and
reinstantiated. Regression coverage is in
WorkerTest.ApplicationCfmRecompilesAfterSourceChange,
WorkerTest.ApplicationCfcRecompilesAfterSourceChange, and
WorkerTest.ComponentRecompilesAfterSourceChange.
Session CFC graphs are retained in a worker-local live-session cache before JSON
persistence, preserving MangoBlog's session.user Author→Role→Preferences graph
across requests in the default single-worker development server. An older session
already flattened to JSON must be replaced with a fresh session.
Custom-tag templates now keep their private variables scope active while executing,
even when invoked from inside a component method; nested GetBaseTagData() calls
therefore retain values such as MangoBlog's Pages.cfm currentPage, counter,
and to variables.
Query columns used as implicit variables now resolve after the active UDF's local
variables and arguments. This matches Adobe ColdFusion when a query column (for
example i) has the same name as a component method's local loop index. Verified
by tests/cfm/query_local_index_collision_test.cfm and the corresponding JIT
regression test.
Component method calls now preserve an independent local-scope context through
nested member dispatch. Numeric <cfloop> indexes are rebound at the loop-body
entry, after the callee context is active, preventing Queue-style code from
reading the caller's i value. Custom-tag private variables are also lower
precedence than an active nested CFC/UDF local or arguments scope; this fixes
MangoBlog's Queue.getElements() when called from a tag template. Verified with
the Queue/nested-holder fixtures under tests/cfm/; the HTTP MangoBlog request
now passes the Queue stage and reaches its login/database setup.
| Tag | Implemented | Notes |
|---|---|---|
| cfabort | ✅ Yes | Compiler + runtime |
| cfajaximport | ❌ No | |
| cfajaxproxy | ❌ No | |
| cfapplet | ❌ No | |
| cfapplication | ✅ Yes | Compiler + runtime. Enables the APPLICATION scope and (with sessionmanagement="true") the SESSION scope, loading or creating them in the SQLite-backed scope store. Attributes: name (unnamed apps use a fixed "" key), sessionmanagement, applicationtimeout/sessiontimeout (numeric days or d,h,m,s; default CF Admin values when omitted), setclientcookies (default yes), searchimplicitscopes (default yes/true matching ColdFusion — unqualified names search the implicit scopes CGI/URL/FORM/COOKIE), loginstorage (session stores the <cflogin> auth key in the session scope instead of the default CFAUTHORIZATION_<apptoken> cookie). Sessions are minted per request when the client has no valid CFID/CFTOKEN cookie; the CFID comes from a server-global counter and CFTOKEN is a <16 lowercase hex>-<uppercase UUID> pair, emitted as Set-Cookie: CFID=..; Expires=<30y>; Path=/; HttpOnly exactly like CF. cf_application_enable() now skips the reset-and-reload when the application scope already has data (populated by a previous onApplicationStart), so live Component instances — whose method tables are lost through JSON round-trips — survive across requests. On the first request (empty scope) the store is loaded as before. ScopeStore supports atomic lifetime extension (touchApplication, touchSession using conditional UPDATE with sqlite3_changes()), system-wide earliest expiry querying (earliestExpiry), and atomic retrieval + deletion of expired scopes (claimExpired). Verified byte-for-byte against CF 2025 in tests/cfm/cfapplication__test.cfm + WorkerTest.Scope + ScopeStoreTest. |
| cfargument | ✅ Yes | Tag form: <cfargument name=".." type=".." required=".." default=".."> inside <cffunction> declares a parameter. default follows CF tag-attribute semantics (a quoted value is a literal string unless #...#-wrapped, e.g. default="1+1" → the string "1+1", default="#1+1#" → 2 — verified on CF); required="true" with no default throws CF's The X parameter to the Y function is required but was not passed in. (MissingArgumentException). <cfargument> outside <cffunction> is rejected at compile time (CF renders a server error). |
| cfassociate | ✅ Yes | Compiler + runtime |
| cfauthenticate | ❌ No | |
| cfbreak | ✅ Yes | Compiler + runtime |
| cfcache | ✅ Yes | Compiler + runtime |
| cfcalendar | ❌ No | |
| cfcase | ✅ Yes | Handled within cfswitch |
| cfcatch | ✅ Yes | Script form: catch (type var) { } clauses dispatch by type via the CF exception-class model: cf_eh_best_match (src/core/core_eh.cpp) implements findThrowableTarget/findCustomTarget exactly like CF 2025. Built-in runtime errors match by the closest class ancestor across ALL clauses (order-independent; ties by clause order): catch types resolve to CF classes (expression→RuntimeException, application→ApplicationException, database→DatabaseException, template→TemplateException, missinginclude→MissingIncludeException, security→AccessControlException, object→ObjectException, searchengine→SearchEngineException, any→Exception), so catch(expression) catches every built-in runtime error (division by zero, undefined var, ... all RuntimeExceptions), a real 1/0 (ExpressionException) is NOT caught by catch(application), and a missing <cfinclude> (MissingIncludeException) matches catch(missinginclude)/catch(template)/catch(expression)/catch(any). Custom throw/<cfthrow> types match by exact name over any (a later catch(myType) beats an earlier catch(any), dotted-prefix partial matches work: throw(type="my.custom.type") → catch(my.custom)/catch(my)), and never match catch(expression)/catch(application) — CF's findCustomTarget. Reserved built-in type names in throw/<cfthrow> (database, template, security, object, missinginclude, expression, lock) are replaced by an ApplicationException reporting type Application with "Attribute validation error for tag CFTHROW." / "... function throw." (CF 2025's ThrowTag.validate), so throw(type="database") does NOT match catch(database). User-thrown custom exceptions carry a custom-ness flag (webstrada::exception::m_isCustom, persisted through capture→rethrow via cfvariant::m_isCustomException) so <cfrethrow> keeps exact-name matching. cfabort never catches; cfcatch binds the caught struct (TYPE/MESSAGE/DETAIL/ERRORCODE/EXTENDEDINFO/TAGCONTEXT) and may be chained. TAGCONTEXT is populated by the call-stack (stacktrace) feature: the JIT pushes a {template pathname, line} frame at every template / UDF / component-method / component-construction entry, updates the top line before each statement and pops on every exit (normal and unwinding); the first landing pad an unwinding exception reaches snapshots the live stack into the in-flight exception (m_stackTrace), so cfcatch.tagContext is an array of {TEMPLATE, LINE} structs with element [1] = where the error occurred, matching CF (byte-verified: tests/cfm/stacktrace_*_test.cfm, StackTraceTest unit tests). A rethrow/the unmatched cftry path re-raise a fresh exception carrying the original tagContext (cf_eh_throw rebuilds m_stackTrace from the captured TAGCONTEXT), like CF rethrowing the same Java exception. Tag form: <cfcatch type="t" var="name"> inside <cftry>; <cfcatch> outside a <cftry> is rejected at compile time. Verified byte-for-byte against CF 2025 in tests/cfm/cftry_tag_test.cfm + cfcatch_class_matching_test.cfm + JitExpressionTest.Catch*/CustomThrow*/ReservedType*/CfincludeTest.MissingIncludeMatchesMissingIncludeHierarchy unit tests (was BUGS.md "cfcatch type matching is exact-name only"). |
| cfchart | ❌ No | |
| cfchartdata | ❌ No | |
| cfchartseries | ❌ No | |
| cfchartset | ❌ No | |
| cfclient | ❌ No | |
| cfclientsettings | ❌ No | |
| cfcol | ❌ No | |
| cfcollection | ❌ No | |
| cfcomponent | ✅ Yes | Compiler + runtime. .cfc files compile into a component definition (ComponentInfo): the top-level <cfcomponent> body becomes a construction function that runs at instantiation (setting this./variables. members), <cffunction> blocks become JIT-compiled methods (component_method_entry_fn, direct calls with the instance's variables/this scopes), <cfproperty> declarations feed SerializeJSON and GetComponentMetaData. Components are first-class values (cfvariant::Component): this scope (public members, keys uppercased like CF) doubles as the struct surface (IsStruct/StructKeyList/SerializeJSON/o.x work on it), variables scope holds private instance variables, unqualified reads resolve local → variables → this (never the caller's page), unqualified writes go to localScope for declared local names (var) and to the component variables scope otherwise, and methods are invoked by direct JIT entry with CF access control (private methods are not callable externally; StructKeyExists/StructKeyList hide them). During method execution, rt->currentPath is set to the component's file path so relative component/include paths resolve against the component directory. Component dot-paths (createObject("component", "a.b.c"), new a.b.c(), extends, implements) resolve relative to the current template/component directory first and fall back to the web root (rt->webRoot), matching ColdFusion; dot paths with directories or filenames containing literal dots (e.g. MangoBlog_1.4.3) use backtracking on-disk resolution to resolve correctly. Function argument and return types with custom component names (e.g. returntype="Setup") validate against the component/interface hierarchy via IsInstanceOf. this.mappings in Application.cfc is supported: custom directory mappings set in Application.cfc's pseudo-constructor (e.g. this.mappings["/org/mangoblog"] = ...) are registered on the application context and used to resolve dot/path component references (createObject("component", ...), new ..., extends="...", implements="...") and <cfinclude template="..."> paths. super. explicitly dispatches methods to parent implementations across multi-level inheritance chains (super.method()). Script-form components (component { ... } at the top of a .cfc) are supported: the textparser grammar's overrideStartTokens parses a .cfc that starts with component/import/interface/final/abstract or a ////* comment as a top-level ScriptExpression, and compileComponent detects that form, parses the component attributes (extends="..", etc.), collects property name=".." type=".." ...; declarations, script methods (with public/private/package/remote/final access modifiers, return types either before the name or as returntype="..", default params) and compiles the remaining body statements as the construction body. Verified byte-for-byte against CF 2025 in tests/cfm/component_*_test.cfm + tests/cfm/mappings_test/ + ComponentTest unit tests. |
| cfcontent | ✅ Yes | Compiler + runtime. Sets the response MIME type and output character encoding from the type attribute (text/html; charset=ISO-8859-1), discards prior output by default (reset, default yes) or preserves it (reset="no"), and can replace the page output with a file's (file, optional deletefile) or variable's (variable) contents (binary passthrough; all other page output is ignored). The charset is applied to the whole buffered response and to the Content-Type header, and can be changed freely until the first byte is written to the web engine — after that, type/charset changes are silently ignored like ColdFusion. A reset inside <cfsilent> clears the whole page output (not just the discard buffer), matching CF 2025 (was BUGS.md #10) — verified in tests/cfm/cfcontent_inside_cfsilent_test.cfm. Encodings: UTF-8 (default, from server config), ISO-8859-1 / latin1 / latin-1 / 8859_1, Unicode (= UTF-16 with BOM), UTF-16LE/BE, US-ASCII; unknown names throw CF's "Unsupported encoding format" error. The send path (sendEncoded, common.cpp) writes UTF-8/binary output straight from the buffer (zero copy) and converts other charsets into one reusable thread_local scratch (no per-flush allocation; stringToBytes gained a std::vector<char>& overload — was TODO Option C, implemented 2026-08-10). Verified byte-for-byte against CF 2021 in tests/cfm/cfcontent_*_test.cfm. |
| cfcontinue | ✅ Yes | Compiler + runtime |
| cfcookie | ✅ Yes | Compiler + runtime (tag_weboutput.cpp). Defines a browser cookie: name (required, uppercased unless preserveCase), value (default "", stripCRLFd, URL-encoded like CF's URLEncoder — only A-Za-z0-9 pass through, everything else %XX uppercase-hex UTF-8, space→%20 — unless encodevalue="no"), expires ("now"→0, "never"→946080000 (30y), "session"→session cookie, M/D/YY[YY] slash dates → diff from now, else 86400×N days; a non-numeric/date value throws CF's "The value X cannot be converted to a number." Expression error), secure/httpOnly/path (default /)/domain/samesite (default /). The Set-Cookie bytes reproduce CF's CookieScope exactly: plain non-empty cookies without SameSite go through Tomcat (value quoted + Version=1 + Max-Age=N when the value is not a token — the separator set "(),:;<=>?@[\]{} + space/controls — and the Expires as RFC1123 EEE, dd MMM yyyy HH:mm:ss GMT, Thu, 01 Jan 1970 00:00:10 GMT for maxAge 0); empty values and SameSite cookies go through CF's own createCookieHeader (raw value or "", Max-Age=N; Expires=<RFC850 12-hour EEE, dd-MMM-yyyy hh:mm:ss GMT>). The raw (unencoded) value lands in the current request's COOKIE scope under the (uppercased) name. Validation matches CF: a name with a space/comma/semicolon or $-prefixed (or empty) throws the Application error "An error has occurred while trying to create a cookie with name X."; a missing name and unknown attributes are compile-time errors with CF's exact messages. After the response is committed the header is dropped but the COOKIE scope entry is still set (verified on CF). Verified against CF 2025 in tests/cfm/cfcookie_test.cfm 🟢 + WebOutputTest.Cookie* unit tests (the Set-Cookie bytes themselves, which the byte-verifier cannot see). |
| cfdbinfo | ✅ Yes | Compiler + runtime |
| cfdefaultcase | ✅ Yes | Handled within cfswitch |
| cfdirectory | ✅ Yes | Compiler + runtime (tag_directory.cpp). list (default action) builds CF's FileListTable query — NAME, SIZE, TYPE, DATELASTMODIFIED (rendered as MM/dd/yyyy hh:nn:ss tt, CF's java.util.Date cell rendering), ATTRIBUTES (empty on Linux), MODE (always empty), DIRECTORY, LINK — into the name variable, with the filter pipe-delimited wildcard, recurse (CF's recursiveGetFiles order), sort (CF's MultiColumnSorter: comma-delimited `COLUMN [ASC |
| cfdocument | ❌ No | |
| cfdocumentitem | ❌ No | |
| cfdocumentsection | ❌ No | |
| cfdump | ✅ Yes | Compiler + runtime |
| cfelse | ✅ Yes | Handled within cfif |
| cfelseif | ✅ Yes | Handled within cfif |
| cferror | ✅ Yes | Compiler + runtime |
| cfexchangecalendar | ❌ No | |
| cfexchangeconnection | ❌ No | |
| cfexchangecontact | ❌ No | |
| cfexchangeconversation | ❌ No | |
| cfexchangefolder | ❌ No | |
| cfexchangefilter | ❌ No | |
| cfexchangemail | ❌ No | |
| cfexchangetask | ❌ No | |
| cfexecute | ✅ Yes | Compiler + runtime (tag_execute.cpp). Runs the name executable with the (Unix-tokenized) arguments and captures stdout to the page, the variable attribute or the outputfile, and stderr to errorVariable/errorFile. Faithfully mirrors CF 2025's ExecuteTag + ProcessExecutor: string arguments are tokenized with getProcessCommand's rules (space-separated, double-quoted segments kept as one argument, a quote mid-argument is literal; array arguments map element-per-arg), relative output/error file paths resolve against the CFML temporary directory (GetTempDirectory), and the timeout (seconds) limits how long CF waits for the process output — with no timeout CF does not wait at all (ProcessExecutor's non-blocking race: blocking is false and timeout is 0, so neither waitFor nor the join runs and the output is read immediately — on CF 2025 the variable stays undefined and the page gets nothing; this engine reproduces the race and leaves the variable unbound). With a timeout the tag waits for both output streams to hit EOF (the process to exit) and captures the output; a deadline miss throws CF's catchable Application "The requested action did not complete in a timely manner. Timeout period expired without completion of <name>." (message + detail). A failed exec throws "An exception occurred when invoking an external process." with the detail "The cause of this exception was that: java.io.IOException: Cannot run program "<name>": error=N, <strerror>." Valid attributes are ARGUMENTS,ERRORFILE,ERRORVARIABLE,NAME,OUTPUTFILE,TIMEOUT,VARIABLE; name is required and unknown attributes are compile-time errors with CF 2025's JSP-style message. Verified byte-for-byte against CF 2025 in tests/cfm/cfexecute_test.cfm (🟢; the no-timeout race is covered by unit tests) + CfexecuteTagTest unit tests. |
| cfexit | ✅ Yes | Compiler + runtime |
| cffeed | ✅ Yes | Compiler + runtime (tag_feed.cpp). action "read" parses an RSS (0.9x/1.0/2.0) or Atom 1.0 feed from a URL (libcurl) or an XML file into the name feed struct (metadata + item/entry array), the properties metadata struct, the query entries query (CF's FeedTable column set) and/or the raw xmlvar; action "create" renders an RSS 2.0 or Atom 1.0 feed from the name struct (or query+properties+columnmap) into xmlvar/outputfile (with the overwrite flag). The generated XML reproduces Rome's WireFeedOutput pretty-print byte-for-byte (CRLF line separators, 2-space indent, <?xml version="1.0" encoding=".."?> header), the RSS channel/item element order (title/link/description/language/rating/copyright/pubDate/lastBuildDate/docs/managingEditor/webMaster/skipHours/skipDays/cloud/generator/ttl/category/image/textInput + items; items: title/link/description/source/enclosure/category/pubDate/expirationDate/author/comments/guid), the Atom feed/entry element order (title/links/category/author/contributor/subtitle/id/generator/rights/icon/logo/updated; entries: title/links/category/author/contributor/id/updated/published/content/summary), the RFC822 (EEE, dd MMM yyyy HH:mm:ss GMT) and W3C (yyyy-MM-dd'T'HH:mm:ss'Z') date rendering (string values parsed with Rome-style W3C/RFC822 parsing, offsets converted to the GMT-based render), the metadata/item struct key set (pubDate strings, description {type,value} for RSS >= 0.94 and Content structs for Atom, guid {value,isPermaLink}, enclosure/category/author arrays, cloud/image/textInput sub-structs) and the FeedTable query columns. Enclosures are parsed into the struct/query but not downloaded (enclosureDir/overwriteEnclosure/ignoreEnclosureError are accepted). Byte-verified against CF 2025 in tests/cfm/cffeed_test.cfm (🟢 — the feed package was installed on the RDS host on 2026-08-10, see BUGS_CF.md) + CffeedTagTest unit tests. |
| cffile | ✅ Yes | Compiler + runtime (tag_file.cpp). Actions read/readBinary (into variable), write/append (from the output attribute or the captured tag body; addnewline default yes; fixnewline; write/append to a missing parent throws CF's FileNotFoundException message), copy/move/rename (source→destination; a destination that is an existing directory (or ends with /) lands the file inside; a missing source throws CF's messages; copy-over-itself is rejected), delete (missing file → CF's "File ... specified in action delete does not exist."), upload/uploadall (multipart via UploadRegistry; the 23-key CF result struct, nameconflict error/overwrite/skip/makeunique, result default "cffile"). After every action the (empty) cffile struct is created; the result attribute is honored only for upload/uploadall (like CF 2025). Unknown attributes are compile-time errors with CF's message; a static invalid ACTION is a compile-time error; missing output/body for write/append, output+body together, and unknown actions are catchable Application errors with CF's text. An un-closed <cffile> (no </cffile>) is a single tag: the tag-body scanner (scanTagBody) treats a missing end tag as body-less instead of swallowing the rest of the template/CFC into a dead block (was the MangoBlog "PARENT" Preferences error — <cffile action="read"> in PreferencesFile.cfc silently dropped every function after it, leaving variables.xmlData as xmlnew(false) and making XmlSearch("/preferences/root") return nothing). Same fix for un-closed <cfzip>. Byte-verified against CF 2025 (tests/cfm/cffile_test.cfm 🟢 + cffile_unclosed_tag_test.cfm + CfzipTagTest.Unclosed* unit tests). |
| └ action="append" | ✅ Yes | |
| └ action="copy" | ✅ Yes | |
| └ action="delete" | ✅ Yes | |
| └ action="move" | ✅ Yes | |
| └ action="read" | ✅ Yes | |
| └ action="readBinary" | ✅ Yes | |
| └ action="rename" | ✅ Yes | |
| └ action="upload" | ✅ Yes | |
| └ action="uploadAll" | ✅ Yes | |
| └ action="write" | ✅ Yes | |
| cffileupload | ❌ No | |
| cffinally | ✅ Yes | Tag form: <cffinally>...</cffinally> inside <cftry> runs on success, after a caught exception, and before an uncaught one re-raises; an exception raised inside the finally replaces the original. Compiled (duplicated) by the shared native-EH emitter in both the tag and script paths. <cffinally> outside a <cftry> is rejected at compile time. Verified byte-for-byte against CF 2025 in tests/cfm/cftry_tag_test.cfm. |
| cfflush | ✅ Yes | Compiler + runtime. Flushes the buffered output to the web engine mid-request (writing the Content-Type header on the first flush), which locks the output charset/type — later <cfcontent> changes are ignored. An empty <cfflush> still commits the response (sends headers / marks the response committed), matching CF 2025: CF locks the header/charset on the first <cfflush> even when nothing is buffered, so a later <cfcontent> charset change is ignored (was BUGS.md " with an empty buffer" — previously an empty flush sent no byte and did not commit; verified against CF 2025 in tests/cfm/cfflush_empty_commits_test.cfm). Flushes encode the buffered UTF-8 via sendEncoded (zero-copy for UTF-8/binary, reusable scratch otherwise — see the cfcontent row). The interval attribute is accepted but not implemented (auto-flush). Verified against CF 2021/2025 in tests/cfm/cfflush_test.cfm. |
| cfform | ❌ No | |
| cfformgroup | ❌ No | |
| cfformitem | ❌ No | |
| cfftp | Compiler + runtime (tag_ftp.cpp). The tag is handler-validated in CF, not TagLibraryInfo-validated, so unknown attributes are accepted and ignored exactly like CF 2025 (verified on the RDS host); only a statically missing action is a compile-time error with CF's message ("Attribute validation error for the CFFTP tag." / "The tag requires the ACTION attribute."). The evaluated attributes are collected into a struct and the runtime cf_ftp_tag only logs the call ([WebStrada] <cfftp> is not implemented; call logged (action=.., server=.., ...)) to the engine log on stderr. No FTP operation is performed — the actual open/close/file/dir actions, secure FTP and action="listDir" remain unimplemented (no FTP client is wired in); CF's per-action required-attribute rules (e.g. action="open" requires SERVER,USERNAME) are NOT enforced. |
|
| └ file/dir operations | ❌ No | (accepted, call logged only) |
| └ open/close connections | ❌ No | (accepted, call logged only) |
| └ secure FTP connections | ❌ No | (accepted, call logged only) |
| └ action="listDir" | ❌ No | (accepted, call logged only) |
| cffunction | ✅ Yes | Tag form: <cffunction name=".." returntype=".." output=".." access="..">...<cfargument>...<cfreturn>...</cffunction> defines a callable UDF. Functions are hoisted across the template (callable before their textual definition, including when the block sits inside <cfif>/control flow, like CF) and registered into the variables scope. The body is compiled like a template (tags, text, <cfoutput>, <cfscript>, nested <cfloop>/<cfif>/<cftry>), sharing the script-form UDF runtime: the arguments scope (upper-cased param keys + positional keys), var-declared names (<cfset var x=..> / <cfscript>var x;</cfscript>) function-local, unqualified non-var writes leaking to the caller's scope (CF's classic gotcha), typed cfargument/returntype coercion (type="struct" / returntype="struct" now accepts component instances (cfvariant::Component), matching Adobe ColdFusion where components are structs/Maps — previously rejected component arguments with "not of type struct", breaking MangoBlog's Event.cfc:setMessage(createObject("component", "Message")); type="xml" accepts an XML document; verified in tests/cfm/udf_component_as_struct.cfm + UdfTypeCoerceTest.ComponentAcceptedAsStruct), and nested <cffunction> registered in the enclosing function's local scope. The output attribute defaults to displayed (output="true" / omitted pass the body's output through, including the body's leading whitespace collapsing to a space; output="false" discards it via the <cfsilent> discard buffer — cf_silent_end is invoked in the function's exit and exception landing pads so early <cfreturn> inside output="false" does not leak the silent buffer to the caller). <cfargument> defaults follow CF tag-attribute semantics (a quoted default="..." is a literal string unless #...#-wrapped, e.g. default="1+1" → the string 1+1, default="#1+1#" → 2); a present-but-empty default="" now binds the empty string (previously left the argument undefined, which changed LSParseDateTime(s, arguments.loc) from CF's English-US empty-locale resolution to the current locale — tracked via a new paramHasDefaultRaw flag in the UDF definition, verified in LocaleTest.TagFormEmptyStringDefaultBindsEmpty). <cfreturn expr> stores the coerced value and exits the function (bare <cfreturn> returns undefined; a void function returning a value throws CF's IllegalReturnException). <cffunction>/<cfreturn> outside a function are compile-time errors (CF renders a server error; a stray nested <cffunction> is skipped). Verified byte-for-byte against CF 2025 in tests/cfm/cffunction_tag_test.cfm + cffunction_edge_test.cfm + UdfTest.TagFunction* unit tests. Exceptions thrown inside a UDF body (script or tag form) are caught by a page-level <cftry>/script try in the caller and by try/catch inside the function — fixed by reusing the single module-wide __gxx_personality_v0 in the JIT (a duplicate created when a try was compiled was auto-renamed __gxx_personality_v0.1, whose .eh_frame references MCJIT could not resolve, leaving external relocations unpatched → SIGSEGV; see llvm_compiler.cpp). Verified byte-for-byte against CF 2025 in tests/cfm/udf_exception_test.cfm. |
| cfgraph | ❌ No | |
| cfgraphdata | ❌ No | |
| cfgrid | ❌ No | |
| cfgridcolumn | ❌ No | |
| cfgridrow | ❌ No | |
| cfgridupdate | ❌ No | |
| cfheader | ✅ Yes | Compiler + runtime |
| cfhtmlhead | ✅ Yes | Compiler + runtime (tag_weboutput.cpp). Appends text to the response head content, which CF writes before the body output no matter where the tag appeared in the page; multiple calls concatenate in order. The worker's final send and the CLI both emit the head content ahead of the body. text is required (compile-time "Attribute validation error for tag CFHTMLHEAD. It requires the attribute(s): TEXT."); unknown attributes are compile-time errors ("... It does not allow the attribute(s) BOGUS. The valid attribute(s) are TEXT."); a body form (<cfhtmlhead>...</cfhtmlhead>) is rejected like CF (no body support). Verified against CF 2025 in tests/cfm/cfhtmlhead_test.cfm 🟢 + WebOutputTest.Htmlhead* unit tests. |
| cfhtmltopdf | ❌ No | |
| cfhtmltopdfitem | ❌ No | |
| cfhttp | ✅ Yes | Compiler + runtime |
| cfhttpparam | ✅ Yes | Compiler + runtime |
| cfif | ✅ Yes | Compiler + runtime |
| cfimage | ✅ Yes | Compiler + runtime |
| cfimap | ❌ No | |
| cfimapfilter | ❌ No | |
| cfimpersonate | ❌ No | |
| cfimport | ✅ Yes | Compiler + runtime |
| cfinclude | ✅ Yes | Compiler + runtime |
| cfindex | ❌ No | |
| cfinput | ❌ No | |
| cfinsert | ✅ Yes | Compiler + runtime |
| cfinterface | ✅ Yes | Compiler + runtime |
| cfinvokeargument | ✅ Yes | Compiler + runtime |
| cfinvoke | ✅ Yes | Compiler + runtime. <cfinvoke component=".." method=".." returnvariable=".." argumentcollection=".."> invokes a component method; component is a component value or a component path (a path string is instantiated on the fly), argumentcollection is a struct of named arguments, returnvariable stores the result. With no component the method resolves as a page UDF. <cfinvokeargument> children add named arguments (see the cfinvokeargument row). Verified against CF 2025 in tests/cfm/component_invoke_test.cfm + cfinvokeargument_test.cfm. |
| cfmodule | ✅ Yes | Compiler + runtime |
| cfjava | ❌ No | |
| cflayout | ❌ No | |
| cflayoutarea | ❌ No | |
| cfldap | ❌ No | |
| cflocation | ✅ Yes | Compiler + runtime |
| cflock | ❌ No | |
| cflog | ✅ Yes | Compiler + runtime |
| cflogin | ✅ Yes | Compiler + runtime (tag_login.cpp) |
| cfloginuser | ✅ Yes | Compiler + runtime (tag_login.cpp) |
| cflogout | ✅ Yes | Compiler + runtime (tag_login.cpp) |
| cfloop | ✅ Yes | Compiler + runtime |
| └ index loop | ✅ Yes | from/to/step; 64-bit bounds so values beyond int32 iterate correctly; body text (including a body that is only plain text, and text before the first nested CFML token) is emitted like CF 2021 — verified byte-for-byte in tests/cfm/cfloop_text_body_test.cfm + LoopPlainTextBodyIsEmitted unit test (previously BUGS.md #1). from/to/step accept CF's bound grammar: a numeric literal (integer or float, whitespace-trimmed), a single #...#-wrapped expression (to="#x + y#", to="#5+2#"), or a mix of literal text and #...# interpolations that concatenate before the numeric cast (to="#x##y#" → "57", to="#x#5" → "55", from="5#x#" → 55) — a bare non-literal expression (to="1 + 2", to="(1 + 2)") is a compile error exactly like CF 2025 (was BUGS.md #26: bounds only unwrapped a single #...#; multi-interpolation failed to compile while CF concatenates the interpolated parts before the cast). Verified byte-for-byte against CF 2025 in tests/cfm/cfloop_bounds_interpolation_test.cfm + LoopBoundsInterpolated/LoopBoundsMultiInterpolation/LoopBoundsFloatLiterals/LoopBoundsBareExpressionRejected/LoopBoundsBeyondInt32 unit tests. #...# in the loop body is context-dependent and matches CF 2021: outside <cfoutput> a bare #q# is emitted verbatim (textparser LoopExpression contextNestedTokens nests SharpExpression only when the loop is lexically inside <cfoutput>/<cfquery>), while inside <cfoutput> it is evaluated (<cfoutput><cfloop from="1" to="2" index="i">#i#</cfloop></cfoutput> → 12) and an escaped ## renders a single # (<cfoutput>#i#:##;</cfloop> → 1:#;2:#;); the ## SharpChar in an output/loop body now emits # instead of nothing (compile_token_list gained a SharpChar case, which also fixed a plain <cfoutput>P##Q</cfoutput> → P#Q) — verified byte-for-byte against CF 2021 in tests/cfm/cfloop_sharp_context_test.cfm + LoopSharpContext unit test (was BUGS.md #1). The index variable is assigned like an unqualified <cfset> (was a WebStrada divergence that broke MangoBlog's Preferences.cfc): inside a function a var-declared loop index is written to the function's local scope (a component method's variables is the instance scope, so writing the index there left the body reading an empty value), a non-var index writes to the component variables / captured parent scope, a parameter-named index to arguments, and at template level to the variables scope — via the new udfAssignScope helper used by cfloop_set_long (numeric) and cfloop_assign_index (list/array/collection), plus the cfscript for (x in coll) iteration variable. A non-var list/numeric index in a plain (non-CFC) UDF also now leaks to the calling page's variables scope like CF. Verified byte-for-byte against CF 2025 in tests/cfm/cfloop_component_scope_test.cfm + ComponentTest.Cfloop*/CfscriptForInIndexScopesToLocalVar/UdfTest.XmlTypedParam* unit tests |
| └ list loop | ✅ Yes | list (+ delimiters, default ,) with an index variable; list expression attrs (list="#x#") supported — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm |
| └ array loop | ✅ Yes | array="#arr#" with an index variable iterating array values — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm |
| └ collection loop | ✅ Yes | collection="#struct#" with an item variable iterating struct keys (uppercased as CF stores literal keys) — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm |
| └ condition loop | ✅ Yes | condition="expr" while-style loop — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm |
| └ query loop | ✅ Yes | query="q" iterates the query's rows, advancing the query's cursor so #q.col# / #q.currentrow# (and #q.recordcount# / #q.columnlist#) resolve to the current row. A pushed query scope (CF's pushQueryScope) makes unqualified names that match a column (or currentrow/recordcount/columnlist) resolve to the current row before the variables scope (<cfset name="VARSCOPE"> + a name column → the column wins; a non-matching name falls through to variables) — verified byte-for-byte against CF 2025 in tests/cfm/cfloop_query_basic_test.cfm + CfLoopQueryTest unit tests. startrow/endrow bound the range (absolute row numbers; a startrow past the last row skips the body entirely, and endrow beyond the row count stops at the last row, matching CF). group="col" runs the first row of each group (adjacent equal values), with groupcasesensitive="true" switching from the default case-insensitive to case-sensitive grouping — verified byte-for-byte in tests/cfm/cfloop_query_variants_test.cfm + cfloop_query_groupcase_test.cfm. <cfbreak>/<cfcontinue> work inside the loop; query="#varName#" resolves a string as the query variable name; a non-query value throws. After the loop the cursor is restored to its original row and the query scope is popped, so #q.currentrow# after a loop is back to 1 (like CF). The query scope stack is reset per request in scope_begin. Query-column references now stringify/compute as their current row's cell (#q.id#, q.id EQ 2, arithmetic, truthiness), which is what makes q.col and unqualified columns work inside the loop while preserving the existing first-cell behavior when the cursor has not moved (verified: x = q["a"]; #x# → first cell, and the whole tests/cfm/query_column_copy_test.cfm suite still passes). |
| └ conditional loop | ❌ No | |
| └ COM collection/structure | ❌ No | |
| └ date/time range | ❌ No | |
| └ list, file, or array | ❌ No | |
| cfmail | ❌ No | |
| cfmailparam | ❌ No | |
| cfmailpart | ❌ No | |
| cfmap | ❌ No | |
| cfmapitem | ❌ No | |
| cfmediaplayer | ❌ No | |
| cfmenu | ❌ No | |
| cfmenuitem | ❌ No | |
| cfmessagebox | ❌ No | |
| cfNTauthenticate | ❌ No | |
| cfoauth | ❌ No | |
| cfobject | ✅ Yes | Compiler + runtime. <cfobject type="component" name=".." component=".."> instantiates the component and assigns it to the name variable; other object types throw "not supported". Verified against CF 2025 in tests/cfm/component_invoke_test.cfm. |
| └ .NET object | ❌ No | |
| └ COM object | ❌ No | |
| └ component object | ❌ No | |
| └ CORBA object | ❌ No | |
| └ Java/EJB object | ❌ No | |
| └ web service object | ❌ No | |
| cfobjectcache | ✅ Yes | Compiler + runtime |
| cfoutput | ✅ Yes | Compiler + runtime |
| cfparam | ✅ Yes | Compiler + runtime |
| cfpdf | ❌ No | |
| cfpdfform | ❌ No | |
| cfpdfformparam | ❌ No | |
| cfpdfparam | ❌ No | |
| cfpdfsubform | ❌ No | |
| cfpod | ❌ No | |
| cfpop | ❌ No | |
| cfpresentation | ❌ No | |
| cfpresentationslide | ❌ No | |
| cfpresenter | ❌ No | |
| cfprint | ❌ No | |
| cfprocessingdirective | ✅ Yes | Compiler + runtime (codegen_tags.cpp + src/template_reader.cpp). suppressWhitespace and pageEncoding are compile-time directives. suppressWhitespace="no" disables whitespace management inside the body (whitespace-only regions between tags are kept verbatim, e.g. <cfoutput>F</cfoutput>\n<cfoutput>G</cfoutput> → F\nG); "yes"/absent keeps the default management (collapse, e.g. → F G). The value must be a static literal (a dynamic #x# value cannot be compiled — whitespace management is a compile-time decision here; documented in BUGS.md); an invalid static boolean is a compile-time error with CF's "Attribute validation error for CFPROCESSINGDIRECTIVE. The value of the SUPPRESSWHITESPACE attribute is invalid..." message. pageEncoding drives the input-encoding pipeline (template_reader.cpp, implemented 2026-08-10): the source is decoded to UTF-8 following CF's order (UTF-8/UTF-16LE/UTF-16BE BOM is authoritative and a contradictory directive is a compile error — "Cannot use the charset X because the file has a Byte Order Mark indicating it uses the charset Y."; without a BOM the directive re-reads the file with the named charset, else ICU4C detection (min confidence 100) runs, else the config::defaultInputCharset fallback (default UTF-8)). Unknown names → CF's "The specified page encoding, X, is not supported." (empty renders ''); a non-literal #x# value → CF's "Expression at line N has to be constant value." (the line comes from the textparser line map, so multibyte UTF-8 before the directive cannot shift it); unquoted values (pageEncoding=UTF-8) are honored. The script-component form pageencoding ".."; is also supported (skipped at codegen; the reader applies it). Latin-1/windows-1252/ISO-8859-*/Shift_JIS/etc. sources re-decode to correct UTF-8 output. Unknown attributes are compile-time errors ("... It does not allow the attribute(s) BOGUS. The valid attribute(s) are SUPPRESSWHITESPACE."). Verified against CF 2025 in tests/cfm/cfprocessingdirective_test.cfm 🟢 + input_encoding_*_test.cfm (9 byte-verified fixtures) + WebOutputTest.ProcessingDirective*/TemplateEncodingTest/ComponentTest.ScriptFormPageEncodingDirectiveIsSkipped unit tests (see BUGS_CF.md for the RDS host's disabled ICU detection). |
| cfprocparam | ✅ Yes | Compiler + runtime |
| cfprocresult | ✅ Yes | Compiler + runtime |
| cfprogressbar | ❌ No | |
| cfproperty | ✅ Yes | Compiler + runtime. Inside <cfcomponent>, declares component properties (name/type/default/access). Properties are metadata: they are not this-scope members (o.prop1 is undefined, matching CF 2025), but SerializeJSON includes their defaults (coerced to their declared property types: numeric/boolean/string) and respects this-scope overrides. StructKeyList, StructKeyArray, and SerializeJSON list component keys in ColdFusion's Java HashMap bucket iteration order. GetComponentMetaData lists them. Verified against CF 2025 in tests/cfm/component_property_test.cfm + component_property_type_test.cfm + component_order_test.cfm. |
| cfquery | ✅ Yes | Compiler + runtime |
| cfqueryparam | ✅ Yes | Compiler + runtime |
| cfregistry | ❌ No | |
| cfreport | ❌ No | |
| cfreportparam | ❌ No | |
| cfrethrow | ✅ Yes | Script form: rethrow; inside a catch re-raises the exception caught by the innermost enclosing catch to the next enclosing catch (or out of the template), skipping the remainder of the inner try. Compiled as cf_eh_throw(captured) + CreateUnreachable(). Rejected at compile time outside a catch (CF silently aborts there — a CF-side behavior, see BUGS_CF.md). Tag form: <cfrethrow> inside <cfcatch> does the same via g_currentCatchExn; <cfrethrow> outside a catch is rejected. Verified byte-for-byte against CF 2025 in tests/cfm/cfscript_throw_rethrow_test.cfm + tests/cfm/cftry_tag_test.cfm + JitExpressionTest.ScriptRethrow*/TagRethrow* unit tests. |
| cfreturn | ✅ Yes | Tag form: <cfreturn [expr]> inside a <cffunction> body stores the (return-type-coerced) expression in the function's return slot and jumps to the common exit block; a bare <cfreturn> returns undefined. Works from inside <cfif>/<cfelseif>/<cfloop> bodies. <cfreturn> outside a function is a compile-time error (CF silently aborts the rest of the template — a deliberate divergence, see BUGS_CF.md). Verified against CF 2025 in tests/cfm/cffunction_tag_test.cfm + UdfTest.TagFunction* unit tests. |
| cfsavecontent | ✅ Yes | Compiler + runtime (codegen_tags.cpp + tag_weboutput.cpp). Saves the body's generated content (with whitespace management applied) into the variable name: pushes a capture buffer (like <cfsilent>), compiles the body into it, then assigns the captured text via cfvariant_assign (a dotted/scoped/dynamic name resolves like an assignment — variable="a.b" and variable="#n#" work). variable is required (compile-time "Attribute validation error for tag CFSAVECONTENT. It requires the attribute(s): VARIABLE."); unknown attributes are compile-time errors ("... It does not allow the attribute(s) BOGUS. The valid attribute(s) are VARIABLE."); an invalid variable name (validated with CF's CFVariableLexer state machine, which allows leading digits like 1abc) throws "Cannot set variable with name X." at tag start. An exception in the body still stores the partial content before propagating (CF's SaveContentTag.doCatch): the body is wrapped in a catch-all that assigns the captured buffer and pops it, then re-raises (verified byte-for-byte). Inside <cfsetting enablecfoutputonly> the body text is suppressed like CF (the capture comes out empty). Verified against CF 2025 in tests/cfm/cfsavecontent_test.cfm 🟢 + WebOutputTest.Savecontent* unit tests. |
| cfschedule | Compiler + runtime (tag_schedule.cpp). The tag is handler-validated in CF, not TagLibraryInfo-validated, so unknown attributes are accepted and ignored exactly like CF 2025 (verified on the RDS host); only a statically missing action is a compile-time error with CF's message ("Attribute validation error for the CFSCHEDULE tag." / "The tag requires the ACTION attribute."). The evaluated attributes are collected into a struct and the runtime cf_schedule_tag only logs the call ([WebStrada] <cfschedule> is not implemented; call logged (action=.., task=.., ...)) to the engine log on stderr. No scheduling is performed — no task is created/updated/deleted/run, action="list" produces no query; the scheduler engine is not wired in (the CF 2025 RDS host lacks the scheduler package too — see BUGS_CF.md), and CF's per-action required-attribute rules (e.g. action="list" requires RESULT) are NOT enforced. |
|
| cfscript | ✅ Yes | Compiler + tests |
| cfsearch | ❌ No | |
| cfselect | ❌ No | |
| cfservlet | ❌ No | |
| cfservletparam | ❌ No | |
| cfset | ✅ Yes | Compiler + runtime |
| cfsetting | ✅ Yes | Compiler + runtime (tag_weboutput.cpp). enablecfoutputonly="true" flips a per-request runtime output mode that suppresses plain text written outside <cfoutput> tags (including the whitespace-collapse space between tags and text captured into <cfsavecontent>/<cfxml> bodies, exactly like CF's CFOutput.enablecfoutputonly); <cfoutput> content is never suppressed. The codegen routes every non-<cfoutput> plain-text write through the gated writers cf_write_output_gated/cf_whitespace_space_gated (checked only when compiling outside a <cfoutput> body, via the g_insideCfoutput compile-time thread-local), so the mode is a simple runtime boolean. enablecfoutputonly="false" turns it back off; the mode is reset per request (scope_begin). showdebugoutput and requesttimeout are accepted (this engine has no debug output section or request watchdog). Unknown attributes are compile-time errors ("Attribute validation error for tag CFSETTING. It does not allow the attribute(s) BOGUS. The valid attribute(s) are ENABLECFOUTPUTONLY,REQUESTTIMEOUT,SHOWDEBUGOUTPUT."). Verified against CF 2025 in tests/cfm/cfsetting_test.cfm 🟢 + WebOutputTest.Setting* unit tests. |
| cfsharepoint | ❌ No | |
| cfsilent | ✅ Yes | Compiler + runtime |
| cfslider | ❌ No | |
| cfspreadsheet | ❌ No | |
| cfsprydataset | ❌ No | |
| cfstoredproc | ✅ Yes | Compiler + runtime |
| cfswitch | ✅ Yes | Compiler + runtime |
| cftable | ❌ No | |
| cftextarea | ❌ No | |
| cftextinput | ❌ No | |
| cfthread | ❌ No | |
| cfthrow | ✅ Yes | Script form: throw "msg";, throw <expr>;, bare throw;/throw(), and named attributes throw(type="t", message="m", detail="d", errorcode="e", extendedinfo="i"). Throws a fresh runtime exception from the attribute struct (cf_eh_throw); built-in messages default to type Application; the custom type participates in catch matching (case-insensitive). Both bare (Variable-token) and parenthesized (Function-token) forms dispatch to compile_script_throw_statement. The thrown struct is built in IR via cfvariant_create_struct + cfvariant_index_assign. The statement is terminal (CreateUnreachable after the throw). A complex object thrown as the message (throw s where s is a struct/array/query, or a literal {a:1}/[1]) now throws CF's Expression Complex object types cannot be converted to simple values. with CF's standard detail (was BUGS.md #7 — previously an empty Application exception; verified byte-for-byte against CF 2025 in tests/cfm/script_throw_complex_message_test.cfm + JitExpressionTest.ScriptThrowComplexMessage). Tag form: <cfthrow type="t" message="m" detail="d" errorcode="e" extendedinfo="i"> raises the same exception; attribute values are compiled from the tag's Expression children so quoted values interpolate #expr# (message="at#i#" → at2) and unquoted bare words stay literal strings (CF tag semantics); unknown attributes are ignored. Verified byte-for-byte against CF 2025 in tests/cfm/cfscript_throw_rethrow_test.cfm + tests/cfm/cftry_tag_test.cfm + JitExpressionTest.ScriptThrow*/TagThrow* unit tests. (The bare-throw literal-first arithmetic quirk — CF takes only 5 from throw 5+3 while this engine evaluates the whole expression — is a CF-side erratic behavior, see BUGS_CF.md.) |
| cftimer | ✅ Yes | Compiler + runtime |
| cftooltip | ❌ No | |
| cftrace | ✅ Yes | Compiler + runtime |
| cftransaction | ✅ Yes | Compiler + runtime |
| cftree | ❌ No | |
| cftreeitem | ❌ No | |
| cftry | ✅ Yes | Script form: try { } catch (type var) { } finally { } compiled to native Itanium EH in the JIT (invoke/landingpad, __gxx_personality_v0). finally runs on success, after a caught exception, and before an uncaught one re-raises; an exception raised inside finally replaces the original; nested try/catch/finally and try-in-loop verified against CF 2021 in tests/cfm/cfscript_try_catch_test.cfm. Uncaught exceptions are snapshotted (cf_eh_capture) and re-raised (cf_eh_throw) so they keep unwinding to an enclosing try or the request handler; an uncatchable abort is re-raised as an abort_exception (see cfabort row). throw/rethrow in script are implemented: throw raises a fresh exception (see cfthrow row); rethrow; inside a catch re-raises the caught exception to the next enclosing catch (it targets the innermost enclosing catch, g_currentCatchExn set/restored around catch-body compilation; rethrow; outside a catch is rejected at compile time with 'rethrow' is only valid inside a catch block, where CF silently aborts — a CF-side behavior, see BUGS_CF.md). Tag form <cftry>...<cfcatch>...</cfcatch><cffinally>...</cffinally></cftry> compiles to the same EH via compile_tag_try_statement (scans the sibling tokens, tracks nested <cftry> depth, compiles each body region through compile_token_list with its own whitespace state); <cfthrow>/<cfrethrow> work inside it. A <cftry> (or script try { }) with neither <cfcatch> nor <cffinally> is now rejected at compile time like CF — "Context validation error in CFTRY block. A CFTRY must contain at least one CFCATCH clause or CFFINALLY clause." / "Context validation error in ''TRY block." for the script form (was BUGS.md " without a cfcatch/cffinally compiles instead of failing"; verified on CF 2025 in JitExpressionTest.CftryWithoutCatch* unit tests). The script and tag forms are verified byte-for-byte against CF 2025 in tests/cfm/cfscript_try_catch_test.cfm + cfscript_throw_rethrow_test.cfm + cftry_tag_test.cfm + cftry_no_catch_compile_test.cfm + abort_inside_try_test.cfm. Note: the CF 2025 test server returns a server-error for <cfloop> directly inside <cftry> (a CF-side limitation; <cftry> inside <cfloop> works) — this engine handles both. |
| cfupdate | ✅ Yes | Compiler + runtime |
| cfwddx | ✅ Yes | Compiler + runtime (tag_wddx.cpp). action cfml2wddx serializes a CFML value to a WDDX 1.0 packet, wddx2cfml deserializes a packet to CFML, cfml2js/wddx2js render JavaScript assignment statements (StringFormatter.scriptFormat). The packet format, char escaping tables (UTF8Converter: control chars as <char code='xx'/>, 128-255 as &#xNN;, entities for <>&, attr table adds '/"), Java Double.toString number rendering (5.0, 3.14, 1.0E10), the ISO8601 dateTime with the local timezone offset (or without when usetimezoneinfo="no"), the recordset (<recordset rowCount fieldNames type='coldfusion.sql.QueryTable'>) and the CF HashMap struct-key iteration order (javaHashMapBucket) are byte-verified against CF 2025. wddx2cfml builds structs (bare-text <var>s are skipped, <null/> becomes the empty string, <char code> decoded), arrays, queries and binaries; cfml2js emits var = new Object();/new Array();/ new Date(y, m0, d, h, min, s); with lowercased struct keys and JS-escaped strings. Per-action attribute validation (CF 2025's TLD): cfml2wddx allows ACTION,INPUT,OUTPUT,USETIMEZONEINFO; wddx2cfml allows ACTION,INPUT,OUTPUT,VALIDATE and requires output; cfml2js/wddx2js allow ACTION,INPUT,OUTPUT,TOPLEVELVARIABLE and require input+toplevelvariable — static action values are validated at compile time ("Attribute validation error for tag CFWDDX. It does not allow the attribute(s) X. ..."), dynamic values at runtime ("... for cfwddx."), with CF's byte-exact messages. Verified byte-for-byte against CF 2025 in tests/cfm/cfwddx_test.cfm (🟢; the RDS host and this machine both run Europe/Skopje UTC+2, so no TZ override is needed — the timezone-dependent dateTime offset and the wddx2cfml no-offset dateTime deserialization match directly) + CfwddxTagTest unit tests. |
| cfwebsocket | ❌ No | |
| cfwindow | ❌ No | |
| cfxml | ✅ Yes | Compiler + runtime |
| cfzip | ✅ Yes | Compiler + runtime (tag_zip.cpp, minizip). Actions zip (from the source file/directory and/or <cfzipparam> children; entries are stored relative to the source directory; filter, recurse, prefix (a top-level directory), entrypath, storePath, overwrite update-vs-create, directory entries stored with a trailing /), unzip (destination required and must already exist — CF's InvalidDestinationException otherwise; entrypath extracts a single entry, filter, overwrite, storePath; path-traversal guarded), list (the 9-column query NAME/DIRECTORY/SIZE/COMPRESSEDSIZE/TYPE/DATELASTMODIFIED/COMMENT/CRC/ENCRYPTIONALGORITHM into name — NAME is the full entry path (directory entries keep the trailing /), DIRECTORY is the parent path for files ("" for directory entries); showDirectory toggles directory entries, filter matches the entry filename), read/readBinary (the entrypath entry into variable), delete (entrypath removed from the archive; the surviving entries are rewritten). A relative file is created under the OS temp directory like CF. Attribute validation is action-aware at compile time like CF 2025: an unknown attribute names the action's valid set ("Attribute validation error for tag CFZIP. It does not allow the attribute(s) X. The valid attribute(s) are ACTION,ENCRYPTIONALGORITHM,FILE,..."), a missing required attribute ("When the value of the ACTION attribute is READ, it requires the attribute(s): ENTRYPATH."), and a bad static ACTION value ("The value of the ACTION attribute, which is currently X, must be one of the values: ZIP,READ,UNZIP,LIST,READBINARY,DELETE."). <cfzipparam> valid attributes are charset/encryptionalgorithm/password/source/content/entrypath/filter/prefix/recurse with CF's compile-time combination validation (content requires entrypath, prefix only with source). Byte-verified against CF 2025 in tests/cfm/tier2_cfzip_test.cfm (🟢) after the CF zip package was installed on the RDS host. |
| cfzipparam | ✅ Yes | Child of <cfzip>: appends source/content/entrypath/filter/prefix/recurse/charset/encryptionalgorithm/password to the active zip context (cf_zip_param); the attribute combination is validated at compile time (content+entrypath, or source, or filter, or entrypath), and a <cfzipparam> outside a <cfzip> throws. Byte-verified with cfzip (tests/cfm/tier2_cfzip_test.cfm 🟢). |
Custom tag invocations are fully implemented (compiler + runtime, tag_custom.cpp + codegen_tags.cpp) and byte-verified against CF 2025. <cfimport prefix=".." taglib=".."> registers a per-template prefix→taglib mapping; <prefix:tag .../> / <prefix:tag>...</prefix:tag> invoke the custom tag template taglib/tag.cfm (relative to the invoking template, or absolute under the web root; subdirectories and a trailing slash in the taglib work). The lifecycle mirrors CF's ModuleTag:
- Start template runs with
thisTag.executionMode = "start"; its output goes straight to the caller's buffer. Body output is captured. End template runs withexecutionMode = "end"andthisTag.generatedContentseeded with the captured body; its own output is captured separately. After the end template, CF'sModuleTag.doAfterBodyordering is reproduced: if the end template assignedthisTag.generatedContent(contentChanged) the new value replaces the body, otherwise the body is kept, and the end template's own output follows both. A self-closing<prefix:tag />is a pair with an empty body (hasEndTag = "YES"); an unterminated tag is start-only (hasEndTag = "NO"). thisTagstores the keysexecutionMode/hasendtag/GeneratedContent(CF's ThistagScope casing; lookups are case-insensitive). AssigningexecutionMode/hasendtagthrowsExpression—The HasEndTag variable in the ThisTag scope cannot be set by the user./The ExecutionMode variable ...(CF's UnableSetThisTagException).attributesholds the invocation attributes (uppercased keys),calleris the invoking template's variables scope (readable and writable —caller.x = vpropagates back), andvariablesis the custom tag's own private scope that persists across the start/end runs and loop iterations.<cfexit>(bare / exittag) in the start template skips the body and end tag (doStartTag returns SKIP_BODY);method="exittemplate"only stops the start template (the body still runs); in the end template both stop the end template (partial output kept).<cfexit method="loop">in the end template re-runs the body + end (fresh body capture each iteration, like CF's EVAL_BODY_AGAIN) and in the start template throws the catchablejakarta.servlet.jsp.JspExceptionExit method Loop not allowed from start tag.<cfmodule template=".." name=".." attributecollection="..">invokes a custom tag by path or name, reusing the same lifecycle. Thetaglibfor prefixed tags may be a subdirectory (taglib="customtags/sub"), and the public names (CF_<NAME>,CF_<filename>) feedGetBaseTagList/GetBaseTagData. Nested custom tags work (a custom tag template may invoke other custom tags /<cfmodule>; each keeps its own scopes on a stack).<cfassociate basetag=".." datacollection="..">appends the subtag's attributes to the base tag'sthisTag.<datacollection>array. A<prefix:tag>whose prefix is not imported is output as literal text (CF leaves it as text); an imported prefix with a missing tag template is a runtimeUnknown tag: <name>.error (CF's is a parse-time error — see BUGS.md).- Valueless attributes — a
<prefix:tag charset />(or<cfmodule ... charset />) attribute with no=/value is passed to the tag as the string"true"(len(attributes.charset) == 4, byte-verified against CF 2025), not as an empty string. The attribute parser (parseTagAttrs, codegen_tokens.cpp) recognizes a trailing/middle valueless attribute (aVariableseparated from the previous value by whitespace and not continuing an operator/dotted expression) instead of swallowing it into the preceding value — previously<cfmodule template="x.cfm" charset />failed to compile with "Unexpected tokens in expression" and<prefix:tag charset />passed""(which then threw "The value cannot be converted to a boolean." in a<cfif attributes.charset>). Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag_valueless_attr_test.cfm + customtags/valueless.cfm + customtags/modvalueless.cfm. - Paired-tag matching is name-boundary aware — the compiler's scan for a custom tag's closing
</prefix:name>usesstartWith("<prefix:name"), which wrongly consumed a nested tag whose name merely starts with the same text (e.g.<mango:Page>'s scan treated<mango:PageProperty …>as a nested<mango:Page>). The parent then never matched its closing tag and ran start-only, so a child'sGetBaseTagData("cf_page")threwTag cf_page was not found in ancestor custom tags.(the Mango Blogtags/mango/PageProperty.cfmfailure). The matcher now requires a tag-name boundary after the pattern (>,/>, whitespace, or end). Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag_pair_prefix_collision_test.cfm +ComponentTest.CustomTagPairPrefixNameBoundary.
Direct custom tags — <cf_name> resolves name.cfm relative to the invoking template and uses the same start/body/end lifecycle as prefixed custom tags. The underscore form is distinct from built-in tags such as <cflayout>. Verified by ComponentTest.DirectCfUnderscoreInvokesCustomTag and tests/cfm/custom_tag_direct_cf_underscore_test.cfm.
Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag__test.cfm + custom_tags.cfm + customtags/ (the shared custom-tag library) + ComponentTest.CfmoduleInvokesCustomTag/CfassociateCollectsSubtagAttributes/CfimportPathRegistersComponent unit tests.
The codegen save/restore of every compile-time thread_local state slot
(g_ehContext, g_currentCatchExn, g_currentFuncCleanupBB,
g_returnCtx, g_compileInFunctionBody, g_currentOut,
g_insideCfoutput) is now exception-safe via ScopedCodegenState<T>
(src/codegen/codegen_internal.h). A compile-time abort (an unimplemented tag,
a parser/expression error) unwinds the compile function without running the
manual restore, so previously the slot was left pointing at dead state and the
NEXT component compile read a garbage landingPadBB and segfaulted in LLVM IR
construction (llvm::Use::set). Minimal repro: a component whose method body
throws at compile time (e.g. the unimplemented <cflock> tag inside a
<cftry>) followed by a normal component — the second compile crashed before
the fix (observed in MangoBlog setup: SubscriptionHandler's setup() aborts on
a <cfschedule> compile abort, then Links/Handler.cfc's compile segfaulted). Now the aborted
compile leaves no residue and the next compile runs normally — verified by
ComponentTest.AbortedComponentCompileDoesNotPoisonNextCompile.
The CFML query runtimes (<cfquery>, queryExecute(), <cftransaction> and the
transaction*() built-ins) execute SQL through an abstract database layer
(include/webstrada/db.h, src/db/db.cpp): cfml::cf_run_query and the
transaction helpers only ever see the webstrada::db::DBConnection /
DBResult / DBDriver interfaces, and the concrete backend is selected per
datasource by the driver registry (registerDriver / openConnection, which
lazily registers the built-in backends).
Backends:
- SQLite (
src/db/db_sqlite.cpp,SqliteDriver+SqliteConnection): each datasource is the per-DSNDNS_<name>.sqlitefile (WAL mode + busy timeout); thesqlite3_prepare_v2multi-statement tail loop,sqlite3_column_*cell conversions, the CF recordcount semantics (DDL → 1, DML → affected rows) and generated-key capture. This is the default backend for a datasource with no config entry (the pre-config behavior). MangoBlog's setup wizard (admin/setup/setup.cfm) now offers SQLite as a database type: choosing it runssetupDatabase()'s<cfinclude template="sqlite.sql">(a SQLite port ofmysql.sql— backtick/charset clauses dropped,KEY/UNIQUE KEY→CREATE INDEX/CREATE UNIQUE INDEX,int(10) unsigned→integer, multi-rowINSERTs kept), creating the 19-table blog schema as aDNS_<name>.sqlitefile when the datasource is configured with the SQLite backend. MangoBlog'sPostsGatewaydate queries are also ported: the MySQL/MSSQLYEAR()/MONTH()/DAY()calls became SQLiteCAST(strftime('%Y'|'%m'|'%d', posted_on) AS INTEGER)(all 27 usages in getByDate/getCountByDate/ getActiveYears/getActiveMonths/getActiveDays), so the calendargetActiveYears/getActiveMonths/getActiveDaysarchives run on SQLite — the CAST is required because SQLite never equates TEXT ("2026", zero-padded "08") with the integer parameters;getByDate/getCountByDateand the blog listing (getAll/getByIds) are still blocked on SQLite by a pre-existing malformedON ... ONjoin chain and a query-columnreturntypeissue (see BUGS.md). Guarded byCfincludeTest.MangoBlogSqliteSetupSchemaRuns/MangoBlogSqliteStrftimeCastDateQueries. - Query of Queries (
src/db/db_qoq.cpp,runQueryOfQueries): implements<cfquery dbtype="query">by loading each referenced query object into a private:memory:SQLite database and executing the SELECT there. Table loading is lazy and schema-driven: SQLite's own prepare reports "no such table: X", the name X is resolved through theQoQResolvercallback (the CFML scope lookup), the query's typed cells are copied into a temporary table (affinity chosen from the cells: INTEGER/REAL/TEXT/BLOB; dates as ISO text so<cfqueryparam>literals compare), and the prepare is retried. The CFQL+concatenation dialect is reproduced from the materialized column affinities (string+string →||, string+number → CF's Database error, number+number →+). See the cfquery PROGRESS row for the verified surface and limitations. - MySQL/MariaDB (
src/db/db_mysql.cpp,MySqlDriver+MySqlConnection, via the MariaDB Connector/C client librarylibmariadb): a datasource configured withbackend="mysql"connects to the server described by its host/port/database/username/password fields.execute()runs the whole SQL script throughmysql_real_querywithCLIENT_MULTI_STATEMENTSand surfaces only the FIRST result; cells are converted from MySQL field types (int → Number/Long, float/decimal → Float, text/char/varchar and text-charset blobs → String, binary blobs → Binary), non-result statements report affected-rows / the auto-incrementGENERATEDKEY(viamysql_insert_id), and transactions use BEGIN/COMMIT/ROLLBACK + SAVEPOINT / ROLLBACK TO.<cfinsert>/<cfupdate>read the table's columns throughSHOW FULL COLUMNS(Key="PRI" marks the primary key). - PostgreSQL (
src/db/db_pgsql.cpp,PostgresDriver+PostgresConnection, via the libpq client library;backend="postgres"with"postgresql"/"pg"aliases): a datasource configured that way connects to the server described by its host/port/database/username/password fields (connection string built from the config;connect_timeoutfrom the query timeout).execute()runs the whole SQL script throughPQsendQuery/PQgetResult(the simple query protocol executes a multi-statement string statement by statement) and surfaces only the FIRST result; cells are converted from PostgreSQL column OIDs (bool → Number 1/0 like CF's PG driver (verified on CF 2025: a PG boolean column renders1/0and its cell class isjava.lang.Integer, nottrue/false— the byte-compare ofpostgres_test.cfmforced this fix), int2/int4/int8/oid → Number/Long with the int32 bound, float4/float8/numeric → Float, bytea → Binary decoding both the\xhex and legacy octal escape forms, date/time/timestamp/interval and everything else → the server's text representation), non-result statements report affected rows (PQcmdTuples, DDL → 1) with no implicit-OID generated key (modern PostgreSQL, matching pgjdbc'sgetGeneratedKeyswithoutRETURNING; anINSERT ... RETURNINGstatement is a result set and surfaces its columns), and transactions use BEGIN/COMMIT/ROLLBACK + SAVEPOINT / ROLLBACK TO SAVEPOINT.<cfinsert>/<cfupdate>read the table's columns frominformation_schema(joined with the PRIMARY KEY constraint in the connection'scurrent_schema()).
A datasource is selected per DSN name by webstrada::config::datasources — a
case-insensitive map (CiStrLess, mirroring ColdFusion's case-insensitive
datasource names) that preserves the casing of the name as first entered, so
appdb / AppDb / APPDB all resolve to the same datasource and the admin
panel shows the name as typed. Connection params come from the config
entry. The daemon now loads them from the server configuration file
(webstrada-config.json, see "Server configuration file" below); the CLI and
unit tests additionally support the WSDATASOURCE_<UPPER_DSN>_* environment
variables at startup (config::loadDatasourcesFromEnv, see config.h). A
datasource with no config entry is treated as SQLite.
db::datasourceConfig(dsn) exposes the resolved parameters (backend +
server fields) to the metadata layer so <cfdbinfo> dispatches its
backend-specific introspection without reopening the connection.
DBResult carries the columns (name/type/values), rowCount, generatedKey
and hasGeneratedKey; cf_run_query maps it to the cfvariant Query object
and the result struct. DBStoredProcResult (added for <cfstoredproc>) carries
resultsets, the statusCode and the outValues; DBConnection::storedProc()
executes a CALL on MySQL/PostgreSQL (the base implementation throws for
backends without stored procedures, e.g. SQLite).
Diagnostics: openConnection wraps every backend connection in a
LoggingConnection (src/db/db.cpp) that dumps every operation on the
abstract DB layer to stdout when webstrada::config::enableQueryLogging is on
(the same flag that gates the [cfquery]/[cfstoredproc] lines in the CFML
layer, and exposed as the admin "Query Logging" setting): [db] <backend> open dsn=.., [db] <backend> execute dsn=.. maxrows=.. + the SQL, begin/
commit/rollback, savepoint <name>/rollbackTo <name>, `tableColumns
Verified with DbLayerTest
unit tests (directly against the abstract interface: select/insert/maxrows/
multi-statement/DDL/errors/transactions/savepoints/driver registry/stored
procedures) + MySqlDbTest and PostgresDbTest unit tests (run against a live
MySQL/MariaDB / PostgreSQL server configured via WSDATASOURCE_*; they skip when
none is reachable) + the existing CfQueryTest/CfTransactionTest/
QueryMutationTest/QueryFunctionsTest suites and byte-for-byte against CF
2025 via the cfquery_*/cftransaction_*/
tier2_queryexecute/cfloop_query_* test files. The mysql_test.cfm,
mysql_backend_test.cfm, postgres_test.cfm and cfstoredproc_test.cfm test
files exercise the MySQL / PostgreSQL backends; mysql_backend_test.cfm and
postgres_test.cfm are byte-verified against CF 2025 (see below) and the
rest cannot be (the RDS host has no stored-procedure datasource).
Live MySQL verification (2026-08-10): a MySQL 26.7.0 server
(mysql:latest Docker container on 192.168.100.10:3306) now hosts the
webstrada database and a dedicated webstrada user (password webstrada_pw,
granted all privileges on webstrada.*). The MySQL backend was exercised
end-to-end against it: the full MySqlDbTest suite (16 tests — select/
persistence/transactions/generated-key/multi-statement/<cfdbinfo> tables,
columns, index, foreignkeys, version, dbnames + <cfstoredproc> out-params,
result-set binding, multiple result sets, DECIMAL scale preservation) passes,
as do mysql_test.cfm and mysql_backend_test.cfm (types incl.
DECIMAL/DATE/TEXT/NULL, GENERATEDKEY, cfqueryparam
integer/varchar/decimal (with scale)/date, <cftransaction> commit+rollback,
update/delete/maxrows/multi-statement first-result-surfaced, LIKE,
<cfinsert>, COUNT) — run through both the CLI and the dev HTTP server.
run-dev.sh registers the mysql/mysqltest/pgtest datasources
(WSDATASOURCE_MYSQL* / WSDATASOURCE_PGTEST_*) and starts http-dev.py,
so any served CFML page can use datasource="mysql" or datasource="pgtest".
Note: MySQL 26.7 compares a DECIMAL(10,2) column against a bare integer
literal with strict scale semantics (12.50 = 12 is FALSE, 12.50 = 12.50 is
TRUE), so decimal cfqueryparam comparisons need a matching scale attribute
— the engine formats them per the documented scale-default (0), matching CF's
QueryParamTag default.
Live PostgreSQL verification (2026-08-10): a PostgreSQL 18.4 server
(postgres:latest Docker container on 192.168.100.10:5433, port = original
5432 + 1) hosts the webstrada database with the dedicated webstrada user.
The full PostgresDbTest suite (18 tests — select/persistence/transactions/
savepoints/affected-rows/INSERT RETURNING/multi-statement/type mappings/
table introspection + <cfdbinfo> tables/columns/index/foreignkeys/version/
procedures + <cfstoredproc> out/inout/null-param/result-set binding) passes,
as does postgres_test.cfm.
Byte-verified against CF 2025 (2026-08-10): the MySQL datasources were
also registered on the RDS host (Connector/J 8 com.mysql.cj.jdbc.Driver,
jdbc:mysql://192.168.100.10:3306/webstrada?sslMode=DISABLED&allowPublicKeyRetrieval=true)
so mysql_backend_test.cfm runs byte-for-byte on both engines (--exact,
🟢). The PostgreSQL datasource was likewise registered on the RDS host
(org.postgresql.Driver, jdbc:postgresql://192.168.100.10:5433/webstrada),
so postgres_test.cfm also byte-verifies (--exact, 🟢). Engine bugs
surfaced by the byte-compares and fixed:
- PG boolean cells are
java.lang.Integer1/0, nottrue/false: CF's PostgreSQL driver surfaces a bool column as 1/0 (SELECT true→1, cell classjava.lang.Integer, SerializeJSON1), sodb_pgsql.cppmapsboolcells to a Number 0/1 (the PK-introspectionis_pkcheck was updated to read the Number). The old "Boolean true/false like the JDBC driver" mapping was wrong. - MySQL DECIMAL cells now stringify with their original scale like CF's
java.math.BigDecimal:12.50not12.5(db_mysql.cppkeeps the raw server text as the Float'sm_literalText;QueryNew/QuerySetCellDECIMAL/NUMERIC columns do the same incoerceQueryCell). Arithmetic still uses the underlying double (q.price + 1→ 13.5), matching CF. - Arithmetic on query-column references and numeric strings was broken
engine-wide:
q.price + 1threw "cannot be converted to an integer" and"12.5" + 1threw instead of 13.5.cfvariant_add/sub/mul/mod/negnow scalarize a query-column ref to its current cell (likecompareVariantsdoes) and coerce numeric strings to numbers like CF's Arith ("2" + 1→ 3,"12.5" + 1→ 13.5,"12.9" MOD 3→ 0,"abc" + 1throws CF's "The value abc cannot be converted to a number."). Verified against CF 2025 on the RDS host; covered byJitExpressionTest.NumericStringArithmeticandJitExpressionTest.QueryColumnRefArithmetic+MySqlDbTest.Decimal*. TheNmulti-statement section ofmysql_backend_test.cfmis the one section not byte-verified: Connector/J rejects a multi-statement script unless the datasource URL enablesallowMultiQueries=true(a CF-side datasource setting, not an engine divergence — the engine's batch execution matches CF with that option on, as the SQLite/SQL Server drivers already verified).
The server configuration now lives in a JSON file (webstrada-config.json).
The daemon resolves it at startup — WEBSTRADA_CONFIG env overrides, else next
to the WebStrada binary (src/config.cpp), the same rule as the SQLite
scope/cache databases — loads it into the config::* globals, and bootstraps
it from the built-in defaults when it does not exist yet (so the file always
reflects the effective configuration). config::save() persists the globals
atomically (temp file + rename). Prefork workers re-read it per request via
config::reloadIfChanged() (a single stat() when unchanged), so an admin
update written by one worker becomes effective on the next request in every
worker without a restart. enableWhitespaceManagement stays compile-time
(affects newly compiled templates only). Wired in appserver::init (before the
fork) + worker::process_request.
Compiler-extension functions (__ prefix, reserved like C's __
identifiers): engine-provided functions callable from CFML, compiled as direct
JIT calls (cf___<name>(args, argc), uniform ABI), never runtime-dispatched
and never shadowable by a UDF/variable. The codegen's generic fallback
(codegen_expr.cpp) emits the direct call and rejects unregistered __name
calls at compile time; the #...# interpreter (core_interp.cpp) dispatches
through the same registry (src/cffunctions/fn_extensions.cpp) and raises a
catchable runtime error for unregistered names. Registered:
__configGet()— struct of the effective config ({settings, datasources}) straight from the globals; datasource passwords masked as****.__configSet({settings?, datasources?})— merge-updates the globals and persists viaconfig::save(). Unknown setting keys / non-numeric numerics / unsupported backends / named arguments are rejected with clear errors; a change tolineExecutionTraceautomatically invalidates and clears the in-memoryTemplateCacheandcustomTagTargetCacheso all templates recompile immediately. password of****keeps the existing secret;{action: "delete"}removes a datasource.__configReset()— resets every config global to the built-in defaults and persists (the panel's "Restore Defaults").__datasourceTest(name)— real connection attempt through the DB layer →{verified, error}.__serverInfo(excludeAdmin)— dashboard runtime stats{state, version, uptimeSeconds, requestsServed, avgResponseMs, lineExecutionTrace, hideAdminRequests, traceSessionCount, recentRequests}tracked per worker process bywebstrada::stats(src/server_stats.cpp;worker::process_requestrecords request begin/end with the response status). An optional truthyexcludeAdminargument drops recent requests whose template path is an admin-panel request —/webstrada/..., the path the panel is served under (deployed at/app/webstradain Docker and mirrored atAPP_ROOT/webstradabyhttp-dev.py);is_admin_request_pathmatches it — the dashboard's "Hide admin requests" switch passes it via?excludeAdmin=trueso the filtering happens server-side.__traceControl(action, value)— controls execution tracing session state. Action"start"clears theWebStrada-profiler.sqlitedatabase and recent requests buffer, resets the session counter, and enableslineExecutionTrace. Action"stop"disableslineExecutionTrace. Action"set_hide_admin"dynamically updates the in-memoryhide_admin_requestssetting (defaulttrue, not persisted inwebstrada-config.json). Whenhide_admin_requestsistrue, requests whose template path is an admin-panel path (/webstrada/...—is_admin_request_path) are skipped from SQLite tracing and do not increment the 100-request session limit.
SQLite Execution Profiler Store (WebStrada-profiler.sqlite).
When lineExecutionTrace is enabled:
- Each line/method transition (
cf_stack_push,cf_stack_set_line,cf_stack_pop) appends aTraceStepinto an in-memory thread-local buffer (g_requestTraceSteps) with zero I/O and sub-10ns overhead during template execution. - After the FastCGI response stream is completed and flushed to the client (
FCGX_Finish_r), the worker persists the request metadata into therequeststable and batch-inserts the entire step timeline into therequest_tracestable via a single SQLite transaction and prepared statement.
Admin panel API + live wiring. The panel now reads/writes the real server
config. CFML endpoints under admin/api/ (config.cfm, datasources.cfm,
serverinfo.cfm) wrap the extension functions over JSON; http-dev.py serves
the built Angular app (admin/dist/webstrada-admin/browser, built with the
relative baseHref: ./ from angular.json) at /webstrada/ with an SPA
fallback and routes
/webstrada/api/*.cfm to the engine. Both the Docker image and the dev server
expose the panel under an APP_ROOT/webstrada layout (the image deploys it at
/app/webstrada, the dev server symlinks the repo's admin/ tree into
APP_ROOT/webstrada at startup), so the engine resolves DOCUMENT_ROOT + REQUEST_URI directly with no rewrite:
the frontend reaches the API through
relative URLs (./api/...) so it resolves against the serving base. The
Angular app (AdminApiService +
HttpClient) wires the Dashboard (live uptime/requests/avg/recent-request
list, "Hide admin requests" switch that filters admin (/webstrada/...)
rows
server-side via
?excludeAdmin=), Settings (editable config with Save + Restore Defaults;
Default Encoding / Default Template Charset are charset comboboxes, the
charsetDetectionMinConfidence default is 80, the two timeouts are rendered as
Days / Hours / Minutes spin boxes, and the Scopes & Storage section was
removed from the UI — scope/cache/dsn paths are still in the config file but
not exposed here) and Data Sources (live list with backend names shown with
proper casing — SQLite/MySQL/PostgreSQL; Refresh Status, Verify All, per-row
Verify, Add a new data source, Edit/update an existing one — the form loads the
row and the name field is read-only while editing — and Delete. SQLite needs
only a name: the server fields are hidden and the database file is
DNS_<name>.sqlite in the datasource DB directory. Datasource names keep
their entered casing: config::datasources is now a case-insensitive map
(CiStrLess, like ColdFusion's case-insensitive datasource names) so appdb /
AppDb / APPDB all resolve, while the panel shows the name as first entered)
and Cache (renamed from "Cached Queries"; lists every live cache-store
entry — region, key, hits, size, created, expiry — with aggregate stat cards
(entries / total hits / cache memory / compiled templates / compiled
components), a Clear All Caches button (__cacheClear), Refresh,
per-row Evict (__cacheEvict) and click-to-sort column headers, all served
by the admin/api/cache.cfm endpoint backed by
__cacheInfo/__cacheEvict/__cacheClear. The two compiled counts come from
webstrada::compiled_cache_counts (template_cache.cpp) — the JIT-compiled
.cfm templates and .cfc components held in this worker's in-memory
TemplateCache, separate from the SQLite store). Debugging & Logging,
Memory & Runtime and Users & Roles still render placeholder data.
Covered by ConfigExtensionTest (17 unit tests: bootstrap/reload/persistence/
validation/password-sentinel/delete/datasource-test/server-info/config-reset/
JIT+interpreter dispatch) and tests/cfm/admin_extension_test.cfm (CLI
regression; not byte-verifiable against CF — these are engine extensions,
running them on CF gives "Variable __CONFIGGET is undefined").
http-dev.py web root + Docker. The dev server's served web root is now
configurable via --webroot DIR or the WEBROOT env var (default: the
directory containing the script), so a mounted host volume can be served at
/ while the binaries, socket/logs and admin panel stay in the app root.
The admin SPA (ADMIN_DIST) and its /webstrada/api/*.cfm endpoints always
resolve against APP_ROOT, independent of the web root (the CFML API gets
DOCUMENT_ROOT=APP_ROOT). At startup ensure_admin_mirror() exposes the repo's
admin/ tree under APP_ROOT/webstrada (symlinking webstrada/api and
webstrada/dist/webstrada-admin/browser to the corresponding admin/ dirs),
so the dev server serves and the engine resolves the admin panel with the same
/webstrada/... paths as production — no URL rewriting. Directory requests
now correctly dispatch
index.cfm through the engine (the extension was previously computed before
index resolution, so / on a dir with only index.cfm was served as static).
The Docker image (build_docker.sh) builds the admin panel in a
node:22-alpine stage (npm ci && npm run build; the relative baseHref: ./
from angular.json resolves correctly under /webstrada/)
and bundles it at /app/webstrada/dist/webstrada-admin/browser plus admin/api at
/app/webstrada/api — the URL-matching layout means nginx hands
/webstrada/api/*.cfm to the engine unmodified (SCRIPT_FILENAME /app$fastcgi_script_name, DOCUMENT_ROOT /app), so
webstrada:latest serves /webstrada/ out of the box.
build_docker.sh default textparser bumped 1.0.7 → 1.0.8 (adds
textparser_set_filename, required by parser.cpp/template_reader.cpp);
Covered by tests/test_http_dev.py (11 routing tests: static/CFML/index resolution/
webroot DOCUMENT_ROOT/admin SPA/admin API/admin+webroot traversal/POST, plus
ensure_admin_mirror() binding).
Detailed Parse Error Diagnostics & Multi-line Operators. On parsing failures in parser::parse (parser.cpp), the exception details now include the target file's full pathname, line number, column number (derived from the textparser line map), and the parser error message (via textparser_parse_error). Set "multiLine": true on Operator in definitions/cfml_definition.json (and regenerated cfml_definition.json.h) so expressions with assignment/operators spanning multiple lines tokenize properly. Covered by ParserErrorDetailsTest.DetailedParseErrorInfo unit test.
Strict Comparison Operators === / !==. The identity-comparison operators === (strict equal) and !== (strict not-equal) are now implemented, mirroring CF 2025's CfJspPage._strictCompare. Unlike ==/!= which coerce across types, === returns true only when both operands share the same type category and are equal within that category:
- Both
Null/NotSet→ strictly equal. - Both
Boolean→ comparetrue/falsedirectly. - Both numeric (
Number,Long,Float,DateTime) → compare as doubles (so1.0 === 1istrue). - Both
String→ case-insensitive string compare (same as==). - Different type categories (e.g.
"1" === 1,true === 1,true === "true") → alwaysfalse.
Precedence is at the same level as ==/!= (level 8). Grammar already included the tokens; only the runtime (compareVariants in core_interp.cpp) and the operator precedence table (codegen_expr.cpp) required updating. Unit tests in tests/cfm/strict_compare1.cfm.
| Function | Return Type | Arguments | Implemented | Notes |
|---|---|---|---|---|
| Abs | numeric | number: numeric |
✅ Yes | |
| ACos | float | number: numeric |
✅ Yes | |
| AddSOAPRequestHeader | ❌ No | |||
| AddSOAPResponseHeader | ❌ No | |||
| AjaxLink | string | url: string |
✅ Yes | Returns the URL unchanged (no Ajax container exists in this engine, matching CF's behavior when HtmlAssembler.getContainerId() is null). Verified against CF 2025 in tests/cfm/tier2_ajax_test.cfm. |
| AjaxOnLoad | void | functionName: string |
✅ Yes | Emits an inline <script> (ColdFusion.Event.registerOnLoad(...)) into the response head, which precedes the body output like CF. Verified against CF 2025 in tests/cfm/tier2_ajax_test.cfm. |
| ApplicationStop | void | () |
✅ Yes | Deletes the current application and all of its sessions from the scope store. The current request keeps its application/session data (verified on CF: StructKeyExists(application, ...) stays YES), but persistence stops, so subsequent requests start fresh. Verified in tests/cfm/cfapplication_stop_test.cfm + WorkerTest.ScopeApplicationStopClearsAppAndSessions. |
| ArrayAppend | boolean | array: array, value: any |
✅ Yes | Mutates array in-place |
| ArrayAvg | numeric | array: array |
✅ Yes | Returns average of array elements |
| ArrayClear | boolean | array: array |
✅ Yes | Clears array in-place |
| ArrayContains | boolean | array: array, value: any |
✅ Yes | Case-sensitive search |
| ArrayContainsNoCase | boolean | array: array, value: any |
✅ Yes | Case-insensitive search |
| ArrayDelete | boolean | array: array, value: any |
✅ Yes | Deletes first occurrence in-place |
| ArrayDeleteAt | boolean | array: array, index: numeric |
✅ Yes | 1-based index |
| ArrayDeleteNoCase | boolean | array: array, value: any |
✅ Yes | Deletes first occurrence case-insensitively in-place |
| ArrayEach | void | array: array, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Invokes callback(item, index, array) for each element (index 1-based); returns null. parallel/maxThreads accepted and ignored. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ArrayEachFilterReduce. |
| ArrayFilter | array | array: array, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Returns a new array with the elements for which the callback returns true (callback args item, index, array). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ArrayEachFilterReduce. |
| ArrayFind | numeric | array: array, value: any |
✅ Yes | Returns first matching 1-based index |
| ArrayFindAll | array | array: array, value: any |
✅ Yes | Returns all matching 1-based indices |
| ArrayFindAllNoCase | array | array: array, value: any |
✅ Yes | Case-insensitive search for indices |
| ArrayFindNoCase | numeric | array: array, value: any |
✅ Yes | Returns first case-insensitive matching 1-based index |
| ArrayInsertAt | boolean | array: array, index: numeric, value: any |
✅ Yes | 1-based index |
| ArrayIsDefined | boolean | array: array, index: numeric |
✅ Yes | Checks if index is defined |
| ArrayIsEmpty | boolean | array: array |
✅ Yes | |
| ArrayLen | numeric | array: array |
✅ Yes | |
| ArrayFirst | any | array: array |
✅ Yes | Returns the first element; empty array throws type java.lang.RuntimeException "Array is empty.Cannot return first element of array." (was BUGS.md #4). Member method arr.first(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm |
| ArrayLast | any | array: array |
✅ Yes | Returns the last element; empty array throws type java.lang.RuntimeException "Array is empty.Cannot return last element of array." (was BUGS.md #4). Member method arr.last(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm |
| ArrayMap | array | array: array, callback: string |
✅ Yes | Returns new array via named callback (ArrayMap(arr, "Abs")) or a closure (ArrayMap(arr, function(x){return x*2;})); source array untouched; callbacks invoked via evaluateExpr/direct UDF invocation (verified in tests/cfm/array2.cfm + ArrayMapNamedCallback/UdfTest.ClosureInExpression unit tests) |
| ArrayMax | numeric | array: array |
✅ Yes | Returns maximum of array elements |
| ArrayMin | numeric | array: array |
✅ Yes | Returns minimum of array elements |
| ArrayNew | array | () |
✅ Yes | 0 args |
| ArrayPop | any | array: array |
✅ Yes | Removes and returns the last element; empty array throws type Expression "Empty Array." (was BUGS.md #4). Member method arr.pop(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm |
| ArrayShift | any | array: array |
✅ Yes | Removes and returns the first element; empty array throws type Expression "Empty Array." (was BUGS.md #4). Member method arr.shift(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm |
| ArrayPrepend | boolean | array: array, value: any |
✅ Yes | Mutates array in-place |
| ArrayReduce | any | array: array, callback: function [, initialValue: any] |
✅ Yes | Reduces the array to a single value: callback(acc, item, index, array) returns the next accumulator, starting from initialValue (without it the accumulator starts null). An empty array returns the initial value unchanged. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ArrayReduce*. |
| ArrayResize | boolean | array: array, size: numeric |
✅ Yes | |
| ArraySet | boolean | array: array, start: numeric, end: numeric, value: any |
✅ Yes | 1-based indices |
| ArraySetMetadata | boolean | array: array, metadata: struct |
✅ Yes | Sets array metadata (returns true) |
| ArraySlice | array | array: array, offset: numeric, length: numeric |
✅ Yes | Returns sub-array |
| ArraySort | boolean | array: array, sortType: string, sortOrder: string |
✅ Yes | Sorts array in-place (supports callbacks) |
| ArraySum | numeric | array: array |
✅ Yes | Returns sum of array elements |
| ArraySwap | boolean | array: array, idx1: numeric, idx2: numeric |
✅ Yes | Swaps elements in-place |
| ArrayToList | string | array: array, delimiter: string = "," |
✅ Yes | |
| Asc | numeric | string: string |
✅ Yes | Returns ASCII value of the first character |
| ASin | float | number: numeric |
✅ Yes | |
| Atn | float | number: numeric |
✅ Yes | Implemented as Atan |
| AuthenticatedContext | ❌ No | |||
| AuthenticatedUser | ❌ No | |||
| BinaryDecode | binary | string: string, encoding: string |
✅ Yes | Encodings: hex, UU, base64, base64URL |
| BinaryEncode | string | binaryData: binary, encoding: string |
✅ Yes | Encodings: hex (uppercase), UU, base64, base64URL; UU uses CF short-form with space padding |
| BitAnd | numeric | number1: numeric, number2: numeric |
✅ Yes | Bitwise AND |
| BitMaskClear | numeric | number: numeric, start: numeric, length: numeric |
✅ Yes | Bitwise mask clear |
| BitMaskRead | numeric | number: numeric, start: numeric, length: numeric |
✅ Yes | Bitwise mask read |
| BitMaskSet | numeric | number: numeric, mask: numeric, start: numeric, length: numeric |
✅ Yes | Bitwise mask set |
| BitNot | numeric | number: numeric |
✅ Yes | Bitwise NOT |
| BitOr | numeric | number1: numeric, number2: numeric |
✅ Yes | Bitwise OR |
| BitSHLN | numeric | number: numeric, count: numeric |
✅ Yes | Bitwise shift-left |
| BitSHRN | numeric | number: numeric, count: numeric |
✅ Yes | Bitwise shift-right |
| BitXor | numeric | number1: numeric, number2: numeric |
✅ Yes | Bitwise XOR |
| BooleanFormat | string | value: any |
✅ Yes | Renders the boolean truthiness of a value as true/false (literal flavor preserved: BooleanFormat(1) → true) |
| CacheGet | any | id: string [, region: string] |
✅ Yes | Reads an object from the sqlite-backed cache store (src/cache_store.cpp + src/cffunctions/fn_cache.cpp). Default region OBJECT; custom regions used as-is. A missing/expired id returns empty. Values are stored as SerializeJSON blobs (queries cached by <cfquery cachedwithin> use a type-preserving format); simple values/structs/arrays round-trip. Not yet byte-verified against CF 2025 (the CF caching package is installed on the RDS host but the Cache* functions still diverge — see BUGS_CF.md); covered by CacheTest + tests/cfm/tier2_cache_test.cfm. |
| CacheGetAllIds | array | [region: string] [, includeExpired: boolean] |
✅ Yes | Returns the ids in a region (default OBJECT), in insertion order. See CacheGet for verification notes. |
| CacheGetMetadata | struct | id: string [, objectType: string] [, region: string] |
✅ Yes | Returns NAME/TIMESPAN/IDLETIME/HITCOUNT/CREATEDTIME/LASTHIT/LASTUPDATED/SIZE for a live entry (empty struct for a missing id). TIMESPAN/IDLETIME are resolved seconds (default 1 day); the time fields are CF DateTimes from the stored epoch ms. See CacheGet for verification notes. |
| CacheGetProperties | struct | [region: string] |
✅ Yes | Returns the named region's properties struct (merged via cacheSetProperties, NAME always present). See CacheGet for verification notes. |
| CacheGetSession | any | objectType: string [, isKey: boolean] |
✅ Yes | Throws Function CacheGetSession is not supported: it returns a Java cache object. — CF returns the underlying Java cache handle, which this engine cannot replicate (same rule as GetPageContext; see BUGS.md). |
| CacheIdExists | boolean | id: string [, region: string] |
✅ Yes | YES/NO for a live (unexpired) entry. See CacheGet for verification notes. |
| CachePut | void | id: string, value: any [, timespan] [, idleTime] [, region] [, throwOnError] |
✅ Yes | Stores an object. timespan/idleTime are decimal days → seconds; omitted → the 1-day default (matching the decompiled GenericEhcache.put); both 0 → eternal. A missing custom region with throwOnError throws. See CacheGet for verification notes. |
| CacheRegionExists | boolean | region: string |
✅ Yes | YES for the standard OBJECT/TEMPLATE/QUERY regions and any created region. See CacheGet for verification notes. |
| CacheRegionNew | void | region: string [, properties] [, throwOnError] |
✅ Yes | Creates a region; throwOnError (default true) throws when it already exists. See CacheGet for verification notes. |
| CacheRegionRemove | void | region: string |
✅ Yes | Removes a custom region and its entries; the standard regions throw. See CacheGet for verification notes. |
| CacheRemove | void | id: any [, throwOnError] [, region] [, exact] |
✅ Yes | Removes ids from a region; id may be a comma-delimited list or an array, and exact="false" matches * wildcards. See CacheGet for verification notes. |
| CacheRemoveAll | void | [region: string] |
✅ Yes | Clears a region's entries (the region itself stays). See CacheGet for verification notes. |
| CacheSetProperties | void | properties: struct [, region: string] |
✅ Yes | Merges a struct into the region's stored properties (read back by cacheGetProperties). See CacheGet for verification notes. |
| CallStackDump | void | [output: string = "browser"] |
✅ Yes | Outputs a text representation of the live CFML call stack (from cfml::g_callStack): one line per frame, innermost first — <template>:<FUNCTION>:<line> for a function/method frame (uppercased name), <template>:<line> for a plain page frame, each newline-terminated. With no argument / "browser" it appends to the page output; a path argument appends to that file (CF 2025 appends, it does not truncate — verified). Byte-verified against CF 2025 in tests/cfm/callstackdump_test.cfm + StackTraceTest.CallStackDump* unit tests. |
| CallStackGet | any | () |
✅ Yes | Returns the live CFML call stack as an array of structs, element [1] = innermost frame (where the call was made), each with the keys Template (full pathname), LineNumber and Function (uppercased function/method name, empty for a plain page / component construction body) — CF 2025's exact key set and casing. Frames come from the stacktrace feature (cfml::g_callStack); anonymous closures report CF's _CF_ANONYMOUSCLOSURE_<n> shape. Byte-verified against CF 2025 in tests/cfm/callstackget_test.cfm + callstack_component_test.cfm + StackTraceTest.CallStackGet* unit tests. |
| CanDeSerialize | boolean | () |
✅ Yes | Returns YES |
| Canonicalize | string | string: string [, restrictMultiple: boolean = false] [, restrictMixed: boolean = false] [, throwOnError: boolean = false] |
✅ Yes | Canonicalizes by decoding HTML, percent and JavaScript encodings (ESAPI Canonicalizer loop order html→percent→js); restrictions throw CF's IntrusionException. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm + JitExpressionTest.Tier2DecodeForHtmlAndCanonicalize. |
| CanSerialize | boolean | () |
✅ Yes | Returns YES |
| Ceiling | numeric | number: numeric |
✅ Yes | |
| CharsetDecode | binary | string: string, encoding: string |
✅ Yes | Converts a string to binary; UTF-8/ISO-8859-1/US-ASCII/UTF-16 (BOM)/UTF-16LE/BE; unmappable chars become ?; Java-style _ aliases accepted |
| CharsetEncode | string | binaryData: binary, encoding: string |
✅ Yes | Converts binary to a string; invalid UTF-8 and unmappable US-ASCII bytes decode to U+FFFD; UTF-16 BOM detection |
| Chr | string | number: numeric |
✅ Yes | Converts a numeric value to a UCS-2 character |
| CJustify | string | string: string, length: numeric |
✅ Yes | Centers a string in a given length |
| Compare | numeric | string1: string, string2: string |
✅ Yes | Returns -1, 0, or 1 |
| CompareNoCase | numeric | string1: string, string2: string |
✅ Yes | Case-insensitive |
| Cos | float | number: numeric |
✅ Yes | |
| CreateDate | DateTime | year: int, month: int, day: int |
✅ Yes | |
| CreateDateTime | DateTime | year: int, month: int, day: int, hour: int, minute: int, second: int |
✅ Yes | |
| CreateEncryptedJWT | ❌ No | |||
| CreateObject | object | type: string, name: string |
✅ Yes | CreateObject("component", "path") instantiates a CFC (dot paths map to directories, relative to the current template, .cfc appended); other types throw. new path(args) in cfscript does the same and auto-calls init(args) (CF behavior). Verified against CF 2025 in tests/cfm/component_*_test.cfm. |
| CreateODBCDate | date | date: date |
✅ Yes | Wraps the input as a DateTime that renders CF's ODBC date form {d 'yyyy-mm-dd'} (Tier 1). Invalid input throws the same two messages as CreateODBCDateTime (non-date string → The value X cannot be converted to a date., out-of-range date shape → X is an invalid date or time string.). Verified against CF 2025 in tests/cfm/tier1_odbc_test.cfm + JitExpressionTest.Tier1CreateOdbcDateAndTime. |
| CreateODBCDateTime | date | date: date |
✅ Yes | Wraps the input (date/string/serial) as a DateTime; renders {ts 'yyyy-mm-dd hh:nn:ss'}, usable in date functions and comparisons (byte-matched against CF 2025); invalid input throws CF's two messages (was BUGS.md #34): a non-date string → The value X cannot be converted to a date., a date-shaped string with an out-of-range component (2026.13.05 10:20, 2026-13-05, 2026.05.32, 2026.05.21 25:00) → X is an invalid date or time string. — the looksLikeDateString helper detects the date shape (verified byte-for-byte in tests/cfm/createodbcdatetime_error_message_test.cfm + JitExpressionTest.CreateOdbcDateTimeInvalidMessage). The string date parser now also accepts dot-separated dates exactly like CF (YYYY.MM.DD, MM.DD.YYYY, DD.MM.YYYY, preferring MM.DD.YYYY when ambiguous, e.g. 2026.05.21 18:22 and 05.06.2026 → May 6), with HH:MM or HH:MM:SS times — this is what makes the CFKillBoard killmail date (2026.05.21 18:22) parse (tests/cfm/createodbcdatetime_dotformats_test.cfm + JitExpressionTest.DateDotSeparatedFormats). |
| CreateODBCTime | date | date: date |
✅ Yes | Wraps the input as a DateTime that renders CF's ODBC time form {t 'hh:nn:ss'} (Tier 1). Invalid input throws the same two messages as CreateODBCDateTime. Verified against CF 2025 in tests/cfm/tier1_odbc_test.cfm + JitExpressionTest.Tier1CreateOdbcDateAndTime. |
| CreateSignedJWT | ❌ No | |||
| CreateTime | DateTime | hour: int, minute: int, second: int |
✅ Yes | |
| CreateTimeSpan | DateTime | days: numeric, hours: numeric, minutes: numeric, seconds: numeric |
✅ Yes | |
| CreateUUID | string | () |
✅ Yes | Returns CF's 8-4-4-16 shape (35 chars: 32 hex + 3 dashes). CreateGUID was removed in CF 2025 (verified on the RDS host). The C rand() generator is now seeded at every process entry point (daemon, forked worker, CLI, unit tests) via cfml::seed_rand(), so a fresh process mints a distinct UUID per call (was BUGS.md "CreateUUID() returns the same value on every run of a fresh process"); covered by JitExpressionTest.CreateUuidIsUniquePerCall. |
| CSRFGenerateToken | string | key: string |
✅ Yes | Returns a 40-character uppercase-hex CSRF token (20 random bytes) stored in the SESSION scope under __CF_CSRF_TOKENS__ keyed by the app name. Verified against CF 2025 in tests/cfm/tier2_csrf_test.cfm. |
| CSRFVerifyToken | boolean | key: string [, token: string] |
✅ Yes | Constant-time comparison of the supplied token against the stored one (first 40 chars). Verified against CF 2025 in tests/cfm/tier2_csrf_test.cfm. |
| CSVProcess | void | path: string, callback: function |
✅ Yes | RFC-4180 reader that invokes the callback for every row (including the header row). RDS host lacks the spreadsheet service (see BUGS_CF.md); implemented in fn_csv.cpp. |
| CSVRead | query | path: string [, firstRowHeader: boolean = true] |
✅ Yes | RFC-4180 CSV parser returning a query; column names from the first row (or columnN). RDS host lacks the spreadsheet service (see BUGS_CF.md); implemented in fn_csv.cpp. |
| CSVWrite | void | path: string, data: query |
✅ Yes | RFC-4180 writer with quoting/escaping. RDS host lacks the spreadsheet service (see BUGS_CF.md); implemented in fn_csv.cpp. |
| DateAdd | DateTime | datepart: string, number: numeric, date: any |
✅ Yes | |
| DateCompare | int | date1: any, date2: any [, datepart: string] |
✅ Yes | |
| DateConvert | DateTime | type: string, date: any |
✅ Yes | |
| DateDiff | numeric | datepart: string, date1: any, date2: any |
✅ Yes | |
| DateFormat | string | date: any [, mask: string] |
✅ Yes | |
| DatePart | numeric | datepart: string, date: any |
✅ Yes | |
| DateTimeFormat | string | date: any [, mask: string] |
✅ Yes | |
| Day | int | date: any |
✅ Yes | |
| DayOfWeek | int | date: any |
✅ Yes | |
| DayOfWeekAsString | string | day_of_week: int [, locale: string] |
✅ Yes | |
| DayOfYear | int | date: any |
✅ Yes | |
| DaysInMonth | int | date: any |
✅ Yes | |
| DaysInYear | int | date: any |
✅ Yes | |
| DE | string | string: string |
✅ Yes | Delay evaluation of string (escapes quotes and wraps in quotes) |
| DecimalFormat | string | number: numeric |
✅ Yes | |
| DecodeForHTML | string | string: string |
✅ Yes | Decodes HTML/XML entities back to characters. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| DecodeFromURL | string | string: string |
✅ Yes | ESAPI inverse of EncodeForURL; '+' becomes space; %XX (case-insensitive) decoded as UTF-8; malformed sequences throw |
| DecrementValue | numeric | number: numeric |
✅ Yes | Returns number - 1 |
| Decrypt | string | string: string, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric |
✅ Yes | Key is base64-decoded; AES/DES/DESEDE/BLOWFISH with mode/padding strings; CFMX_COMPAT throws the Security Provider error (matches server); wrong key throws bad-padding error |
| DecryptBinary | binary | binaryData: any, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric |
✅ Yes | Returns binary; key base64-decoded; CFMX_COMPAT throws (matches server) |
| DeleteClientVariable | boolean | name: string |
✅ Yes | Deletes a client variable. The client scope is not implemented; on the RDS host client storage is enabled and CF's implementation returns true for any name once the client scope exists (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm). See BUGS_CF.md. |
| Deserialize | any | string: string, [type: string] |
✅ Yes | Wrapper, delegates to DeserializeJSON for JSON |
| DeserializeJSON | any | string: string, [strictMapping: boolean = true] |
✅ Yes | Uses json-c; struct keys keep their original casing and lookups are case-insensitive; int64 numbers beyond int32 deserialize to the exact Long type (DeserializeJSON("2147483648") → 2147483648, DeserializeJSON("9007199254740993") → exact; previously truncated to int32) |
| DeserializeXML | xml | xmlString: string [, caseSensitive: boolean [, validator: string]] |
✅ Yes | Alias for XmlParse |
| DirectoryCopy | boolean | source: string, destination: string |
✅ Yes | Copies directory recursively |
| DirectoryCreate | boolean | path: string |
✅ Yes | Creates parents |
| DirectoryDelete | boolean | path: string, recurse: boolean = false |
✅ Yes | |
| DirectoryExists | boolean | path: string |
✅ Yes | |
| DirectoryList | array | path: string |
✅ Yes | Returns array of file paths |
| DirectoryRename | boolean | source: string, destination: string |
✅ Yes | |
| DollarFormat | string | number: numeric |
✅ Yes | |
| DotNetToCFType | ❌ No | |||
| Duplicate | any | value: any |
✅ Yes | Recursively independent copy of structs/arrays/queries (CF reference semantics). Verified against CF 2025 in tests/cfm/tier2_duplicate_test.cfm. |
| EncodeForCSS | string | string: string |
✅ Yes | ESAPI CSS encoder (hex escapes + rgb() triplet preservation). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForDN | string | string: string |
✅ Yes | ESAPI DN encoder (escapes < > and leading/trailing space). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForHTML | string | string: string |
✅ Yes | ESAPI HTML encoder (named entities). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForHTMLAttribute | string | string: string |
✅ Yes | ESAPI HTML-attribute encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForJavaScript | string | string: string |
✅ Yes | ESAPI JavaScript encoder (\xHH escapes). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForLDAP | string | string: string |
✅ Yes | ESAPI LDAP encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForURL | string | string: string [, canonicalize: boolean = false] |
✅ Yes | ESAPI URL encode; alphanumerics and -_. stay literal, space becomes '+', UTF-8 percent-encoding; canonicalize=true decodes %XX sequences first |
| EncodeForXML | string | string: string |
✅ Yes | ESAPI XML encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForXMLAttribute | string | string: string |
✅ Yes | ESAPI XML-attribute encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| EncodeForXpath | string | string: string |
✅ Yes | ESAPI XPath encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm. |
| Encrypt | string | string: string, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric |
✅ Yes | Key is base64-decoded; default ECB/PKCS5; UU/Base64/Hex encodings; CBC auto-generates a random prepended IV; CFMX_COMPAT throws the Security Provider error (matches server) |
| EncryptBinary | binary | binaryData: any, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric |
✅ Yes | Returns binary; CFMX_COMPAT throws (matches server) |
| EntityDelete | ❌ No | |||
| EntityLoad | ❌ No | |||
| EntityLoadByExample | ❌ No | |||
| EntityLoadByPK | ❌ No | |||
| EntityMerge | ❌ No | |||
| EntityNew | ❌ No | |||
| EntityReload | ❌ No | |||
| EntitySave | ❌ No | |||
| EntityToQuery | ❌ No | |||
| Evaluate | any | expression: string, ... |
✅ Yes | Evaluates one or more string expressions dynamically left-to-right, returns the rightmost result; matches evaluating the same expression directly (byte-matched against CF 2025) |
| Exp | float | number: numeric |
✅ Yes | e^number |
| ExpandPath | string | relativePath: string |
✅ Yes | Resolves relative path |
| FileClose | boolean | fileObj: any |
✅ Yes | Closes an open file |
| FileCopy | boolean | source: string, destination: string |
✅ Yes | |
| FileDelete | boolean | path: string |
✅ Yes | |
| FileExists | boolean | path: string |
✅ Yes | |
| FileGetMimeType | string | path: string |
✅ Yes | Extension-based detection |
| FileIsEOF | boolean | fileObj: any |
✅ Yes | |
| FileMove | boolean | source: string, destination: string |
✅ Yes | |
| FileOpen | any | path: string, mode: string |
✅ Yes | Returns file object with fd |
| FileRead | string | path: string |
✅ Yes | |
| FileReadBinary | any | path: any |
✅ Yes | Path string or file object |
| FileReadLine | string | fileObj: any |
✅ Yes | Reads line from file object |
| FileSeek | boolean | fileObj: any, position: numeric |
✅ Yes | Seeks to byte position |
| FileSetAccessMode | boolean | path: string, mode: string |
✅ Yes | Calls chmod |
| FileSetAttribute | boolean | path: string, attr: string |
✅ Yes | No-op on Linux |
| FileSetLastModified | boolean | path: string, date: any |
✅ Yes | Sets mtime via utimensat |
| FileSkipBytes | boolean | fileObj: any, count: numeric |
✅ Yes | Seeks forward by count bytes |
| FileUpload | struct | destination: string, fileField: string, mimeType: string = "", onConflict: string = "error", strict: boolean = true |
✅ Yes | Saves uploaded multipart file; onConflict: error/skip/overwrite/makeunique; non-existent destination becomes a file, per CF |
| FileUploadAll | array | destination: string, mimeType: string = "", onConflict: string = "error", strict: boolean = true, continueOnError: boolean = false, errorVariable: string = "cffile.uploadAllErrors", allowedExtensions: string = "" |
✅ Yes | Saves all multipart files; with continueOnError, failures go to the errorVariable |
| FileWrite | boolean | path: string, content: string |
✅ Yes | Appends a trailing newline for string content like CF (FileWrite(path,"aaaa") → 5-byte file; was BUGS.md "FileWrite() does not append a newline"), matching the <cffile action="write" addnewline="yes"> default; binary content is written verbatim. Verified against CF 2025 in tests/cfm/filewrite_newline_test.cfm + JitExpressionTest.FileWriteAppendsNewline. |
| FileWriteLine | boolean | fileObj: any, content: string |
✅ Yes | Appends \n to content |
| Find | numeric | substring: string, string: string, start: numeric = 1 |
✅ Yes | 1-based, 0 if not found |
| FindNoCase | numeric | substring: string, string: string, start: numeric = 1 |
✅ Yes | Case-insensitive |
| FindOneOf | numeric | set: string, string: string [, start: numeric = 1] |
✅ Yes | 1-based, 0 if not found |
| FirstDayOfMonth | int | date: any |
✅ Yes | |
| Fix | numeric | number: numeric |
✅ Yes | Truncates double toward zero |
| Floor | numeric | number: numeric |
✅ Yes | |
| FormatBaseN | string | number: numeric, radix: numeric |
✅ Yes | 32-bit semantics (verified vs CF 2025): radix 2 and 16 use two's-complement (lowercase) for negatives, all other radixes signed; number is truncated to low 32 bits for values in [-2147483648, 4294967295], outside that range throws |
| Generate3DesKey | string | seed: string |
✅ Yes | Returns base64 of the seed (server behavior); 0 args throws |
| GeneratePBKDFKey | string | algorithm: string, passphrase: string, salt: string, iterations: numeric, keySize: numeric |
✅ Yes | PBKDF2WithHmacSHA1/224/256/384/512; returns base64 of derived key |
| GenerateSAMLSPMetadata | ❌ No | |||
| GenerateSecretKey | string | algorithm: string, keySize: numeric = 256 |
✅ Yes | AES default 256-bit; DES 8, DESEDE 112/168, BLOWFISH 32-448 bits; returns base64 |
| GetApplicationMetadata | struct | () |
✅ Yes | Returns the current application's settings: NAME, APPLICATIONTIMEOUT / SESSIONTIMEOUT (whole seconds, e.g. 93784 for CreateTimeSpan(1,2,3,4)), SESSIONMANAGEMENT, CLIENTMANAGEMENT, SETCLIENTCOOKIES, SETDOMAINCOOKIES, LOGINSTORAGE. This is a subset of CF's large struct; the rest of CF's keys (RESTSETTINGS, MAPPINGS, DATASOURCES, ORMSETTINGS, ...) are not populated. When no application is active (no <cfapplication>/Application.cfc ran) it returns an EMPTY struct like CF (was BUGS.md #3: previously the 8 modeled keys were returned unconditionally) — verified against CF 2025 in tests/cfm/getappmetadata_empty_test.cfm. Verified for the covered keys against CF 2025 in tests/cfm/cfapplication_scope_test.cfm. |
| GetAuthUser | string | () |
✅ Yes | The name of the currently authenticated user (cflogin/cfloginuser), or an empty string when nobody is logged in (CF's UserUtils.getAuthUser). Reads the request's security context installed by the login tags; the raw OS/basic-auth remote user is not modeled. Verified against CF 2025 in tests/cfm/cflogin_basic_test.cfm + LoginTagTest unit tests. |
| GetBaseTagData | struct | (tagName [, instancenumber]) |
✅ Yes | Returns the named ancestor custom tag's page scope struct. CF's PageScope resolves names directly against the base tag's page context, so every variable the tag template set is addressable as data.<var> (e.g. data.currentPage) and the scopes are exposed as THISTAG / ATTRIBUTES / CALLER / VARIABLES keys; the struct mirrors this by merging the tag's variables at the top level beneath the four scope keys. Matching uses CF's _templateName (case-insensitive): cf_<name> for imported/prefixed tags, CF_<name> for <cfmodule> tags; instancenumber selects the Nth matching ancestor. Missing tag → Tag <name> was not found in ancestor custom tags. The variables-at-top-level behavior fixed the Mango Blog "Element CURRENTPAGE is undefined in DATA." error in tags/mango/Page.cfm (GetBaseTagData("cf_pages").currentPage), and the paired-tag prefix-boundary fix (see the Custom tags section) fixed the Mango Blog "Tag cf_page was not found in ancestor custom tags." error in tags/mango/PageProperty.cfm (GetBaseTagData("cf_page",1)). Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag_getbasetagdata_test.cfm + custom_tag_pair_prefix_collision_test.cfm + custom_tag_associate_test.cfm + ComponentTest.GetBaseTagDataResolvesTagVariablesAtTopLevel + ComponentTest.GetBaseTagDataScopesStillExposed + ComponentTest.CustomTagPairPrefixNameBoundary + ComponentTest.CfassociateCollectsSubtagAttributes. |
| GetBaseTagList | string | () |
✅ Yes | Comma-delimited list of the currently-executing base tags from innermost to outermost. The innermost entry is CFOUTPUT only while a <cfoutput> block is executing (a <cfoutput> is CF's innermost base tag); outside one the innermost entry is the current custom tag. Custom tags contribute their public name (CF_<NAME> for imported/prefixed tags, CF_<filename> for <cfmodule> tags), innermost first. Was a bug: the CFOUTPUT prefix was prepended unconditionally, so listdeleteat(getbasetaglist(),1) (Mango Blog's Posts.cfm context detection) removed the wrong entry and Variable POSTS is undefined was thrown. Now the runtime tracks a base-tag stack (g_baseTagStack) with a cf_cfoutput_begin/cf_cfoutput_end marker per <cfoutput> block. Verified byte-for-byte against CF 2025 in tests/cfm/getbasetaglist_{current,ctx,branch2,parent_cfoutput,root}_test.cfm + custom_tag_basetaglist_test.cfm + custom_tag_nested_test.cfm + ComponentTest.GetBaseTagListIncludesCfdoutputOnlyInsideCfdoutput + ComponentTest.GetBaseTagListParentCfdoutputOrdering. |
| GetBaseTemplatePath | string | () |
✅ Yes | |
| GetClientVariablesList | string | () |
✅ Yes | Returns the comma-delimited writable client variables. The client scope is not implemented, so it returns the empty list like CF's empty client scope (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1SimpleFunctions). |
| GetComponentMetaData | struct | obj: component |
✅ Yes | Returns the component's metadata struct (name/path/type/fullname/functions/parameters/properties/extends). Note: the CF 2025 test server throws on GetComponentMetaData(o) ("Complex object types cannot be converted to simple values"), so this is not byte-verified (see BUGS_CF.md). |
| GetContextRoot | string | () |
✅ Yes | Returns the servlet context path; a default CF installation (and the RDS host) reports the empty string (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm). |
| GetCPUUsage | numeric | [interval: numeric = 1000] |
✅ Yes | Returns the CPU usage percentage sampled from /proc/stat over the interval. The RDS host lacks pmtagent (see BUGS_CF.md); covered by JitExpressionTest.Tier2GetCPUUsage-style checks. |
| GetCSPNonce | string | () |
✅ Yes | Returns the request's Content-Security-Policy nonce; like CF when no CSP is configured, it returns the empty string (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1SimpleFunctions). |
| GetCurrentTemplatePath | string | () |
✅ Yes | |
| GetDirectoryFromPath | string | path: string |
✅ Yes | |
| GetEncoding | string | scope: string |
✅ Yes | Returns the character set of the FORM or URL scope ("UTF-8" default, or as set by SetEncoding); any other scope throws CF's "Only form or URL scope is allowed in a SetEncoding() call." (verified against CF 2025 in tests/cfm/tier1_getencoding_test.cfm + tier1_setencoding_test.cfm). |
| GetException | any | object: any |
✅ Yes | Returns the current caught exception; CF only ever returns Java exceptions from its ExceptionCache, so a CFML argument yields empty/null. Verified against CF 2025 in tests/cfm/tier2_getexception_test.cfm. |
| GetFileFromPath | string | path: string |
✅ Yes | |
| GetFileInfo | struct | path: string |
✅ Yes | Keys: exists, size, path, type |
| GetFreeSpace | numeric | path: string |
✅ Yes | Returns the free bytes on the filesystem via statvfs (Java File.getFreeSpace() semantics). CF validates the path shape (length > 1 and starting with /, a drive letter at position 1, or \\); a bare / throws CF's "The value of the path parameter "/" passed to GETFREESPACE function is invalid." (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1DiskSpace). |
| GetFunctionCalledName | string | () |
✅ Yes | Returns the name of the currently executing function (the enclosing cffunction or component method) as called at the call site (preserving casing and alias variable names). Returns the empty string "" when called at page level outside any function. Verified against CF 2021/2025 in tests/cfm/getfunctioncalledname.cfm + GetFunctionCalledNameTest unit tests. |
| GetFunctionList | struct | () |
✅ Yes | Returns a struct of the built-in function names mapped to empty strings, exactly like CF's map built from its Java method names (values verified empty against CF 2025). The engine enumerates its own registry (kBuiltinFunctionNames, 657 names), so the exact key set/casing differs from CF's 800-entry list (see BUGS_COSMETIC.md). Membership checks that agree with CF are byte-verified in tests/cfm/tier1_getfunctionlist_test.cfm. |
| GetGatewayHelper | ❌ No | |||
| GetHttpRequestData | struct | [includeBody: boolean] |
✅ Yes | Returns a struct with the HTTP request data: headers (a struct of the request headers, lowercased names — from the CGI scope's HTTP_* vars; empty when there is no CGI, e.g. the CLI), protocol (same as cgi.server_protocol), method (same as cgi.request_method) and content (the raw request body captured by the daemon worker; the CLI has none). The includeBody argument (default true) controls whether the content key is present at all — GetHttpRequestData(false) omits it entirely, verified on CF 2025. Key insertion order matches CF (headers,protocol,method,content). Verified in tests/cfm/gethttprequestdata_test.cfm + JitExpressionTest.GetHttpRequestData unit tests. |
| GetHttpTimeString | string | [date: any] |
✅ Yes | Formats the date as an RFC 1123 GMT string. CF interprets the date value as the server's local wall clock and renders the resulting instant in GMT: the local offset (DST-aware, resolved via mktime with tm_isdst=-1) is applied, so GetHttpTimeString(CreateDate(2026,6,1)) on a UTC+2 host yields Sun, 31 May 2026 22:00:00 GMT (previously the date was treated as already-UTC → Mon, 01 Jun 2026 00:00:00 GMT). No argument / null → the current UTC time. Byte-verified against CF 2025 in tests/cfm/date_time_functions_test.cfm + JitExpressionTest.GetHttpTimeStringAppliesLocalOffset. |
| GetK2ServerDocCount | ❌ No | |||
| GetK2ServerDocCountLimit | ❌ No | |||
| GetLocale | string | () |
✅ Yes | Returns the current request locale's CF string (default en_US; after SetLocale the display name, e.g. French (Standard)), driven by the same per-request locale state as SetLocale. |
| GetLocaleDisplayName | string | [locale: string = "en"] [, locale: string] |
✅ Yes | 28×28 locale display-name matrix (JDK COMPAT data in src/cffunctions/ldn_table.inc); default "English". Verified against CF 2025 in tests/cfm/tier2_localedisplayname_test.cfm. |
| GetLocalHostIP | string | () |
✅ Yes | Returns the loopback address "127.0.0.1", matching the RDS host (verified against CF 2025 in tests/cfm/tier1_ip_test.cfm). |
| GetMetaData | struct | object: any |
✅ Yes | Component/UDF introspection struct (PATH, EXTENDS, PROPERTIES, FUNCTIONS, TYPE, FULLNAME, NAME); queries and simple values throw CF's error. Verified against CF 2025 in tests/cfm/tier2_getmetadata_test.cfm. |
| GetMetricData | any | mode: string |
✅ Yes | Returns server metrics: simple_load/prev_req_time/avg_req_time as integers (idle → 0) and perf_monitor as a struct with CF's key set (reqqueued, cfcreqqueued, dbhits, wsreqqueued, avgreqtime, bytesout, templatereqtimedout, wsreqrunning, bytesin, pagehits, avgqueuetime, errorcount, instancename, reqrunning, wsreqtimedout, cfcreqtimedout, templatereqrunning, avgdbtime, cfcreqrunning, templatereqqueued). Values are live server counters and cannot be byte-verified against CF (structure verified in JitExpressionTest.Tier1GetMetricData). |
| GetPageContext | any | () |
✅ Yes | Throws Function GetPageContext is not supported: it returns a Java page context object. — CF returns the underlying Java servlet PageContext object (coldfusion.runtime.NeoPageContext), which this engine cannot replicate (same rule as CacheGetSession; see BUGS.md). |
| GetPrinterInfo | ❌ No | |||
| GetPrinterList | ❌ No | |||
| GetProfileSections | any | iniPath: string |
✅ Yes | Returns struct of sections |
| GetProfileString | string | iniPath: string, section: string, key: string |
✅ Yes | Reads key from ini file |
| GetPropertyFile | string | name: string |
✅ Yes | Returns the server properties-file path (Java .properties format). Verified against CF 2025 in tests/cfm/tier2_propertystring_test.cfm. |
| GetPropertyString | string | name: string, key: string [, defaultValue: string] |
✅ Yes | Java .properties parser (key=value, key: value, line continuation, escapes). Verified against CF 2025 in tests/cfm/tier2_propertystring_test.cfm. |
| GetReadableImageFormats | string | `` | ✅ Yes | Comma-separated list of formats the engine can read |
| GetSafeHTML | ❌ No | |||
| GetSAMLAuthRequest | ❌ No | |||
| GetSAMLLogoutRequest | ❌ No | |||
| GetSOAPRequest | ❌ No | |||
| GetSOAPRequestHeader | ❌ No | |||
| GetSOAPResponse | ❌ No | |||
| GetSOAPResponseHeader | ❌ No | |||
| GetSystemFreeMemory | numeric | () |
✅ Yes | Returns the free OS memory in bytes (sysinfo). The RDS host lacks the pmtagent package, so CF 2025 throws "The pmtagent package is not installed." there (see BUGS_CF.md); the byte values cannot be byte-verified (JitExpressionTest.Tier1SystemMemory). |
| GetSystemTotalMemory | numeric | () |
✅ Yes | Returns the total OS memory in bytes (sysinfo). Same RDS host limitation as GetSystemFreeMemory (see BUGS_CF.md; JitExpressionTest.Tier1SystemMemory). |
| GetTempDirectory | string | () |
✅ Yes | |
| GetTempFile | string | directory: string, prefix: string |
✅ Yes | Creates temp file |
| GetTemplatePath | string | () |
✅ Yes | Removed in ColdFusion 2025 (deprecated since CF 6 in favor of GetBaseTemplatePath; the docs say it was dropped). Calling it reports "Variable GETTEMPLATEPATH is undefined." like CF (verified on the RDS host in tests/cfm/tier1_error_test.cfm); the name was also dropped from the built-in function registry. GetBaseTemplatePath/GetCurrentTemplatePath remain the supported forms. |
| GetTickCount | numeric | () |
✅ Yes | Milliseconds |
| GetTimeZoneInfo | struct | () |
✅ Yes | |
| GetToken | string | string: string, index: numeric, delimiters: string |
✅ Yes | Java StringTokenizer semantics: every delimiter character separates tokens, consecutive delimiters collapse and empty tokens are never returned. The default delimiters are whitespace (\r\n\t ), NOT a comma (verified against CF 2025; the decompiled CFPage defaults to "\r\n\t "). An index ≤ 0 throws CF's "The value of parameter 2 of the function GetToken, which is now N, must be a positive integer". Verified in tests/cfm/tier1_gettoken_test.cfm + tier1_error_test.cfm + JitExpressionTest.Tier1GetToken. |
| GetTotalSpace | numeric | path: string |
✅ Yes | Returns the total bytes on the filesystem via statvfs (Java File.getTotalSpace() semantics). Same path-shape validation and error message as GetFreeSpace (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1DiskSpace). |
| GetUserRoles | string | () |
✅ Yes | The comma-delimited list of the current user's roles (the order they were given to <cfloginuser roles=..>), or an empty string when nobody is logged in (CF's UserUtils.getUserRoles). Verified against CF 2025 in tests/cfm/cflogin_roles_test.cfm + LoginTagTest unit tests. |
| GetVFSMetaData | struct | fileSystemType: string |
✅ Yes | Returns metadata struct for the virtual file system (ENABLED=true, LIMIT="20971520", USED="0", FREE="20971520"). Supports "ram" (case-insensitive) or URIs starting with "ram:"; invalid types throw CF's "FileSystemType provided is not valid." (verified against CF 2025 in tests/cfm/tier1_getvfsmetadata_test.cfm). |
| GetWriteableImageFormats | string | `` | ✅ Yes | Comma-separated list of formats the engine can write |
| Hash | string | string: any, algorithm: string = "SHA-256", encoding: string = "UTF-8", additionalIterations: numeric = 0 |
✅ Yes | Uppercase hex; MD5/SHA-1/SHA-256/384/512; iterations chain raw digest bytes; CFMX_COMPAT returns empty (matches server) |
| HMac | string | message: any, key: any, algorithm: string = "HMACMD5", encoding: string = "UTF-8" |
✅ Yes | Uppercase hex; empty key returns empty string (matches server) |
| Hour | int | date: any |
✅ Yes | |
| HQLMethods | ❌ No | |||
| HTMLCodeFormat | string | string: string [, version: numeric] |
✅ Yes | Escapes special chars and wraps the result in <PRE>/</PRE> (uppercase). Escaping (verified byte-for-byte against CF 2025 in tests/cfm/htmlcodeformat_test.cfm, interpreter + cfscript JIT paths): \r is dropped, "→", &→&, ' stays unchanged, <→<, >→>, \n/tabs/% pass through; version is ignored. Port of StringFormatter.escapeXML(xml=false). Note: a # inside a #...# string (©) starts a nested sharp expression on both engines. |
| HTMLEditFormat | string | string: string [, version: numeric] |
✅ Yes | Same escaping as HTMLCodeFormat but without the <PRE> wrapper (port of StringFormatter.htmlEditFormat). version is ignored. Cannot be byte-verified on the RDS host (CF 2025 server lacks htmlEditFormat, see BUGS_CF.md); covered by HtmlEditFunctionsTest unit tests, with the shared escaping byte-verified through HTMLCodeFormat. |
| IIf | any | condition: any, string1: string, string2: string |
✅ Yes | Evaluates the chosen string expression lazily via cf_evaluate. Verified against CF 2025 in tests/cfm/tier2_iif_test.cfm. |
| ImageAddBorder | void | image: image, thickness: numeric, color: string = "black", borderType: string = "constant" |
✅ Yes | Grows the canvas by thickness on all sides; types constant/zero/copy/reflect/wrap; negative thickness error byte-exact vs CF |
| ImageBlur | void | image: image, blurRadius: numeric = 3 |
✅ Yes | Box filter, radius 3..10; out-of-range error byte-exact vs CF |
| ImageClearRect | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric |
✅ Yes | Fills the rect with the background color (pixel-exact vs CF) |
| ImageCopy | image | image: image, x: numeric, y: numeric, width: numeric, height: numeric, dx: numeric, dy: numeric |
✅ Yes | Returns a new image with the cropped rect; with dx/dy also copies the rect within the source (CF copyArea) |
| ImageCreateCaptcha | any | height: numeric, width: numeric, text: string, difficulty: string, font: string, fontsize: numeric |
✅ Yes | Port of coldfusion.image.CaptchaMaker onto cairo (random background gradient, noise curves/polygons, rotated glyph text). Zero width/height are auto-computed like CF's ImageHelper (round(1.5*len*font*0.72+1) / round(2*font*0.72+1)), difficulty low/medium/high, and the argument errors reproduce CF's InvalidCaptchaArgumentException message/detail + UnsupportedCaptchaDifficultyException. Output is random (clock-seeded like CF), so it is verified by unit tests (dimensions, errors, IsImage) rather than byte-compared; the CF 2025 RDS host aborts the request on every ImageCreateCaptcha call (server error, see BUGS_CF.md) |
| ImageCrop | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric |
✅ Yes | Crops in place, clamped to the image bounds; negative dimensions error byte-exact vs CF |
| ImageDrawArc | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric, startAngle: numeric, arcAngle: numeric, filled: boolean |
✅ Yes | Java Arc2D angles (0=3 o'clock, +CCW); filled=PIE wedge. Cairo raster (coarse match vs CF) |
| ImageDrawBeveledRect | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric, raised: boolean, filled: boolean |
✅ Yes | Brighter min(255,c/0.7)/darker c*0.7 tones; raised controls tone layout (pixel-exact vs CF) |
| ImageDrawCubicCurve | void | image: image, ctrlx1: numeric, ctrly1: numeric, ctrlx2: numeric, ctrly2: numeric, x1: numeric, y1: numeric, x2: numeric, y2: numeric |
✅ Yes | Cubic Bezier (start, ctrl1, ctrl2, end); cairo stroke (coarse match vs CF) |
| ImageDrawLine | void | image: image, x1: numeric, y1: numeric, x2: numeric, y2: numeric |
✅ Yes | Axis-aligned lines pixel-exact; diagonals cairo stroke (endpoint pixels may differ by 1) |
| ImageDrawLines | void | image: image, xcords: array, ycords: array, isPolygon: boolean, filled: boolean |
✅ Yes | Polyline/polygon; cairo path (coarse match vs CF) |
| ImageDrawOval | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric, filled: boolean |
✅ Yes | Ellipse; cairo path (coarse match vs CF) |
| ImageDrawPoint | void | image: image, x: numeric, y: numeric |
✅ Yes | (w+1)xw block centered on the point; pixel-exact vs CF |
| ImageDrawQuadraticCurve | void | image: image, x1: numeric, y1: numeric, ctrlx1: numeric, ctrly1: numeric, x2: numeric, y2: numeric |
✅ Yes | Quadratic Bezier (start, control, end); cairo stroke (coarse match vs CF) |
| ImageDrawRect | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric, filled: boolean |
✅ Yes | Fill x..x+w-1; outline spans x..x+w (pixel-exact vs CF) |
| ImageDrawRoundRect | void | image: image, x: numeric, y: numeric, width: numeric, height: numeric, arcwidth: numeric, archeight: numeric, filled: boolean |
✅ Yes | Rounded corners; cairo path (coarse match vs CF) |
| ImageDrawText | void | image: image, str: string, x: numeric, y: numeric, attributes: struct |
✅ Yes | Baseline at (x,y); attributes font/size/style/underline/strikethrough; honors drawing axis + transparency/XOR. Best-effort on cairo: font metrics differ from Java2D and CF 2025 crashes on ALL text rendering, so output is not pixel-verified (see BUGS_CF.md) |
| ImageFlip | void | image: image, transpose: string = "vertical" |
✅ Yes | vertical/horizontal/diagonal/antidiagonal/90/180/270; diagonal/antidiagonal/90/270 swap dimensions; invalid transpose error byte-exact vs CF |
| ImageGetBlob | binary | source: string |
✅ Yes | Bytes of the image in its source format |
| ImageGetBufferedImage | any | name: image |
✅ Yes | CF returns the underlying java.awt.image.BufferedImage (a live Java object); this engine has no Java object interop, so it returns the image itself (the closest stand-in for its backing store). Divergence documented in BUGS.md |
| ImageGetEXIFMetadata | struct | name: image |
✅ Yes | EXIF header struct with com.drew tag names/descriptions (reproduces CF's Image.getExifMetadata), byte-exact JSON vs CF 2025 for the stable IFD0/SubIFD/IPTC cases. Parses JPEG APP1 Exif TIFF (IFD0 + SubIFD + GPS + Interop + Thumbnail); makernotes are not implemented (see BUGS.md). For images with GPS/Interop/Thumbnail the struct key order follows CF's internal directory/tag iteration and may shift across CF restarts (see BUGS_CF.md) — the GPS tag values are verified via the order-independent tag getters. Missing-source images (blank/base64) throw CF's "The ImageMetadata feature is not supported for BLOBS or Base64 strings." |
| ImageGetEXIFTag | string | name: image, tagName: string |
✅ Yes | Case-insensitive EXIF tag lookup by com.drew tag name; missing tags return "". Verified byte-exact vs CF 2025 |
| ImageGetHeight | numeric | source: string |
✅ Yes | Image height in pixels |
| ImageGetIPTCMetadata | struct | name: image |
✅ Yes | IPTC header struct (JPEG APP13 "Photoshop 3.0" IRB 8BIM 0x0404 resource) with com.drew tag names, byte-exact JSON vs CF 2025 |
| ImageGetIPTCTag | string | name: image, tagName: string |
✅ Yes | Case-insensitive IPTC tag lookup by com.drew tag name; missing tags return "". Verified byte-exact vs CF 2025 |
| ImageGetMetadata | struct | source: string |
✅ Yes | PNG/JPEG/GIF metadata struct (byte-exact JSON vs CF). The JPEG struct's key order follows CF's internal HashMap bucket iteration (Image Width before Y Resolution as of the 2026-08-10 CF restart; see BUGS_CF.md — it may shift again on the next restart) |
| ImageGetWidth | numeric | source: string |
✅ Yes | Image width in pixels |
| ImageGrayscale | void | image: image |
✅ Yes | Java luminance 0.299R+0.587G+0.114B; colormodel becomes grayscale/ComponentColorModel |
| ImageInfo | struct | source: string |
✅ Yes | source/width/height/colormodel struct (byte-exact JSON vs CF) |
| ImageMakeColorTransparent | image | image: image, color: string |
✅ Yes | Returns a new image; pixels matching the color become fully transparent (RGB kept); colormodel becomes argb |
| ImageMakeTranslucent | image | image: image, percent: numeric |
✅ Yes | Returns a new image with alpha = 1 - percent/100; out-of-range error byte-exact vs CF |
| ImageNegative | void | image: image |
✅ Yes | Inverts RGB (255 - value), alpha preserved |
| ImageNew | any | source: any, width: numeric, height: numeric, imageType: string, canvasColor: string |
✅ Yes | Blank/from-file image; rgb/argb/grayscale |
| ImageOverlay | void | image1: image, image2: image, rule: string = "src_over", alpha: string = "1.0" |
✅ Yes | Draws image2 over image1 with an alpha-compositing rule (src/src_over/dst_over/src_in/src_out/dst_in/dst_out); invalid rule error |
| ImagePaste | void | image1: image, image2: image, x: numeric, y: numeric |
✅ Yes | Draws image2 over image1 with the top-left at (x,y) (CF paste, bicubic) |
| ImageRead | any | source: string |
✅ Yes | Reads PNG/JPEG/GIF/BMP/PNM from a file |
| ImageReadBase64 | any | data: string |
✅ Yes | Reads image from base64 string (data: URI supported) |
| ImageResize | void | image: image, width: string, height: string, interpolation: string = "highestquality", blurFactor: numeric = 1.0 |
✅ Yes | Scales in place; absolute or % dimensions; one blank side scales proportionally; nearest/bilinear filters (bicubic approximated by bilinear); resolution/dimension errors byte-exact vs CF |
| ImageRotate | void | image: image, x: numeric, y: numeric, angle: numeric, interpolation: string = "nearest" |
✅ Yes | Rotates about (x,y) (default the center; the CF 4-arg form ignores x/y, matching the decompiled CFPage); output sized `ceil(w· |
| ImageRotateDrawingAxis | void | image: image, angle: numeric, x: numeric, y: numeric |
✅ Yes | Post-multiplied (M = M·T), clockwise-positive rotation about (x,y), x/y default 0; pixel-verified vs CF (rotate 90° about any anchor, translate+rotate). cairo rasterization is 1px off Java2D for rotated strokes (coarse match) |
| ImageScaleToFit | void | image: image, fitWidth: string, fitHeight: string, interpolation: string = "highestquality", blurFactor: numeric = 1.0 |
✅ Yes | Resizes keeping the aspect ratio to fit the bounding box (CF scaleToFit semantics) |
| ImageSetAntialiasing | void | image: image, antialias: string |
✅ Yes | on/off (case-insensitive) + true/false; error text byte-exact vs CF |
| ImageSetBackgroundColor | void | image: image, color: string |
✅ Yes | Strict CF color parser (names/hex/"r,g,b"); errors byte-exact vs CF |
| ImageSetDrawingColor | void | image: image, color: string |
✅ Yes | Strict CF color parser; errors byte-exact vs CF |
| ImageSetDrawingStroke | void | image: image, attributes: struct |
✅ Yes | width/endcaps/lineJoins/miterLimit/dashArray/dash_phases; negative width error byte-exact vs CF |
| ImageSetDrawingTransparency | void | image: image, percent: numeric |
✅ Yes | 0..100 percent (SrcOver alpha 1-pct/100); clears XOR mode (last setter wins); error byte-exact vs CF |
| ImageSharpen | void | image: image, gain: numeric = 1.0 |
✅ Yes | Unsharp mask (original + gain·(original - blur)); gain -1..2; out-of-range error byte-exact vs CF |
| ImageShear | void | image: image, shear: numeric, direction: string = "horizontal", interpolation: string = "nearest" |
✅ Yes | Shears about the center; output sized to fit; invalid direction/interpolation errors byte-exact vs CF |
| ImageShearDrawingAxis | void | image: image, shrx: numeric, shry: numeric |
✅ Yes | x'=x+shrx·y, y'=shry·x+y; pixel-verified vs CF (coarse for diagonal strokes) |
| ImageTranslate | void | image: image, xTrans: numeric, yTrans: numeric, interpolation: string = "nearest" |
✅ Yes | Shifts the image by (x,y) keeping the canvas size (clipped); interpolation validation error byte-exact vs CF |
| ImageTranslateDrawingAxis | void | image: image, x: numeric, y: numeric |
✅ Yes | Cumulative origin translation; affects all draw calls AND ImageClearRect (verified vs CF); pixel-exact vs CF |
| ImageWrite | void | image: string, destination: string, quality: numeric, overwrite: boolean |
✅ Yes | Writes PNG/JPEG/GIF/BMP/PNM (JPEG quality check) |
| ImageWriteBase64 | string | image: string, destination: string, format: string, inHTMLFormat: boolean, overwrite: boolean |
✅ Yes | Base64 string; data: URI when inHTMLFormat |
| ImageXORDrawingMode | void | image: image, color: string |
✅ Yes | XOR blend (drawingColor ^ xorColor ^ dst); clears transparency mode (last setter wins); pixel-exact vs CF |
| IncrementValue | numeric | number: numeric |
✅ Yes | Returns number + 1 |
| InitSAMLAuthRequest | ❌ No | |||
| InitSAMLLogoutRequest | ❌ No | |||
| InputBaseN | numeric | string: string, radix: numeric |
✅ Yes | Parses a base-radix string into an integer (radix 2-36) |
| Insert | string | substring: string, string: string, position: numeric |
✅ Yes | Inserts substring after 0-based position |
| Int | numeric | number: numeric |
✅ Yes | |
| InterruptThread | ❌ No | |||
| InvalidateOauthAccesstoken | ❌ No | |||
| Invoke | any | object: string, methodName: string, arguments: any |
✅ Yes | Invokes a CFC method or a page UDF (empty object = current page UDF), with positional array or named struct arguments. Verified against CF 2025 in tests/cfm/tier2_invoke_test.cfm. |
| InvokeCFClientFunction | — | CF 2025 reports Variable INVOKECFCLIENTFUNCTION is undefined., which the engine reproduces (see fn_ajax.cpp). |
||
| IsArray | boolean | value: any |
✅ Yes | Checks if value is an array |
| IsAuthenticated | — | Obsolete pre-MX security function. CF 2025 reports Variable ISAUTHENTICATED is undefined., which the engine reproduces (see fn_isauthenticated.cpp). UDFs with this name are allowed. |
||
| IsAuthorized | — | Obsolete pre-MX security function. CF 2025 reports Variable ISAUTHORIZED is undefined., which the engine reproduces (see fn_isauthorized.cpp). UDFs with this name are allowed. |
||
| IsBinary | boolean | value: any |
✅ Yes | |
| IsBoolean | boolean | value: any |
✅ Yes | |
| IsClosure | boolean | value: any |
✅ Yes | YES for anonymous function values (f = function(x){...}), NO for named UDFs and built-in method handles — verified against CF 2021 (tests/cfm/udf_closure_test.cfm + UdfTest.IsClosureIsCustomFunction) |
| IsCustomFunction | boolean | value: any |
✅ Yes | YES for UDF/closure Function values (named or anonymous), NO for built-in method handles — verified against CF 2021 (tests/cfm/udf_basic_test.cfm + UdfTest.IsClosureIsCustomFunction) |
| IsDate | Boolean | value: any |
✅ Yes | Backed by parseDateTimeStr (see ParseDateTime row). Full consumption is enforced (was BUGS.md "IsDate()/ParseDateTime() ignore trailing garbage"): a trailing non-whitespace tail after the parsed datetime is rejected like CF (IsDate("2020-05-15bogus") → NO); fractional seconds (.ddd right after a seconds field), a trailing timezone offset (Z as the last char, ±HH/±HHMM/±HH:MM), trailing whitespace, and {ts '...'} with trailing text are accepted exactly as CF's CFDateTimeParser does (a parsed +HH:MM offset accepts arbitrary following text in a full date+time string only, matching CF's !hoursGiven short-circuit). Byte-verified against CF 2025 in tests/cfm/isdate_trailing_garbage_test.cfm + isdate_fractional_seconds_test.cfm + JitExpressionTest.IsDateRejectsTrailingGarbage. |
| IsDateObject | boolean | date: any |
✅ Yes | |
| IsDDX | boolean | value: string |
✅ Yes | Reports whether the value is a valid DDX document (parses as XML with the case-sensitive <DDX> root). The RDS host lacks the PDF/DDX service, so CF 2025 always returns NO there (see BUGS_CF.md); the sniffing behavior is covered by JitExpressionTest.Tier1IsDdxAndWddx. |
| IsDebugMode | boolean | () |
✅ Yes | Returns NO — debug output is never enabled (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm). |
| IsDefined | boolean | variable_name: string |
✅ Yes | Resolves a dotted variable name against the runtime scopes: unqualified names search the variables scope (plus the enclosing UDF parent scopes inside a function), scope-prefixed names resolve variables/url/form/cookie/server/application/session/cgi/request, and inside a UDF arguments.* and local.* work (mapped to the current function's local scope). Matches CF: a null/undefined value is NOT defined, a disabled scope (APPLICATION/SESSION before <cfapplication> enables them) is NOT defined, scope names themselves (session, form, ...) are defined once enabled, a bare request is defined (the REQUEST scope exists per request; a missing request.member is NO), empty-string/numeric args are NO, query columns resolve like struct members, and the CF quirk that any cgi.<member> reference (even a non-existent key) is YES is reproduced. A wrong argument count throws IsDefined requires exactly 1 argument. Verified byte-for-byte against CF 2025 in tests/cfm/isdefined_test.cfm + JitExpressionTest.IsDefined* unit tests. |
| IsFileObject | boolean | value: any |
✅ Yes | |
| IsImage | boolean | value: any |
✅ Yes | |
| IsImageFile | boolean | filepath: string, format: string |
✅ Yes | Decodable image file (optional format check) |
| IsInstanceOf | boolean | obj: any, typeName: string |
✅ Yes | True when a component is (or extends) the named type. |
| IsIPv6 | boolean | value: string |
✅ Yes | Reproduces CF's IPAddressUtils.isIPV6: the loopback/hostname forms (localhost, 127.0.0.1, [::1], ::1, 0:0:0:0:0:0:0:1) report whether the server has IPv6 (true on the RDS host), other values resolve and return whether any address is IPv6. IPv4-mapped ::ffff:... addresses report NO like CF. Verified against CF 2025 in tests/cfm/tier1_ip_test.cfm + JitExpressionTest.Tier1IpFunctions. |
| IsJSON | boolean | string: string |
✅ Yes | Uses json-c tokener |
| IsK2ServerABroker | ❌ No | |||
| IsK2ServerDocCountExceeded | ❌ No | |||
| IsK2ServerOnline | ❌ No | |||
| IsLeapYear | boolean | year: int |
✅ Yes | |
| IsLocalHost | boolean | value: string |
✅ Yes | Reproduces CF's IPAddressUtils._isLocalHost: "localhost" (case-insensitive, raw — no trimming), the exact "127.0.0.1", "[::1]" and any IPv6 form resolving to ::1 are localhost; other loopback addresses (127.0.0.2) are not. Verified against CF 2025 in tests/cfm/tier1_ip_test.cfm + JitExpressionTest.Tier1IpFunctions. |
| IsNull | boolean | value: any |
✅ Yes | |
| IsNumeric | boolean | value: any |
✅ Yes | |
| IsNumericDate | boolean | value: any |
✅ Yes | |
| IsObject | boolean | value: any |
✅ Yes | Component and Function (a bare built-in function reference is a method-handle object in CF) |
| isOnline | boolean | host: string |
✅ Yes | Issues a curl HEAD request (5s timeout); true when the request completes. Not defined on the RDS host (CF 2025 removed it); implemented in fn_isonline.cpp. |
| IsPDFArchive | boolean | pdf: any |
✅ Yes | PDF magic sniffing (%PDF- + %%EOF, archived only when a PDF/A marker is present). RDS host always reports NO (no PDF service, see BUGS_CF.md). |
| IsPDFFile | boolean | filepath: string |
✅ Yes | PDF magic-byte sniffing. RDS host always reports NO (no PDF service, see BUGS_CF.md). |
| IsPDFObject | boolean | obj: any |
✅ Yes | PDF magic-byte sniffing. RDS host always reports NO (no PDF service, see BUGS_CF.md). |
| IsProtected | — | Obsolete pre-MX security function. CF 2025 reports Variable ISPROTECTED is undefined., which the engine reproduces (see fn_isprotected.cpp). UDFs with this name are allowed. |
||
| IsQuery | boolean | value: any |
✅ Yes | |
| isSafeHTML | boolean | string: string [, policyFile: string] |
✅ Yes | Validates HTML against AntiSamy policy rules (built-in antisamy-basic.xml or custom policy file). Returns YES/NO. Complex objects throw Expression exception; missing custom policy throws Application exception. Verified against CF 2025 in tests/cfm/issafehtml_test.cfm. |
| isSamlLogoutResponse | ❌ No | |||
| IsSimpleValue | boolean | value: any |
✅ Yes | |
| IsSOAPRequest | ❌ No | |||
| IsSpreadsheetFile | ❌ No | |||
| IsSpreadsheetObject | ❌ No | |||
| IsStruct | boolean | value: any |
✅ Yes | |
| isThreadInterrupted | any | threadName: string |
✅ Yes | CF 2025 takes the thread name (0 args reports "Parameter validation error ... The function takes 1 parameter."). The engine has no thread subsystem (cfthread is unimplemented), so any name reproduces CF's "Thread X cannot be used in action isInterrupted because the thread X was not created." (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1IsThreadInterrupted). |
| IsUserInAnyRole | boolean | value: string |
✅ Yes | YES when the current user has ANY of the comma-delimited roles in value (CF's UserUtils.isUserInAnyRole trims each element; each is then an isUserInRole test). Each role in value is split on , and trimmed; spaces inside an element are preserved. Verified against CF 2025 in tests/cfm/cflogin_roles_test.cfm + LoginTagTest unit tests. |
| IsUserInRole | boolean | value: string |
✅ Yes | YES when the current user has ALL of the comma-delimited roles in value (CF's UserUtils.cflogin_IsUserInRole: every group must be present — IsUserInRole("role one,role two") is YES only when both are in the user's roles). Roles are split on , without trimming (a space inside an element stays part of it, matching the roles attribute semantics). Verified against CF 2025 in tests/cfm/cflogin_roles_test.cfm + LoginTagTest unit tests. |
| IsUserLoggedIn | boolean | () |
✅ Yes | YES when a user is logged in, i.e. GetAuthUser() is non-empty (CF's CFPage.IsUserLoggedIn: !"".equals(GetAuthUser())). Reads the request's security context installed by the login tags. Verified against CF 2025 in tests/cfm/cflogin_basic_test.cfm + LoginTagTest unit tests. |
| IsValid | boolean | type: string, value: any [, min] [, max] [, pattern] |
✅ Yes | Validator matrix: numeric/integer/range/uuid/guid/email/ssn/telephone/zipcode/creditcard/url/date/time/boolean/string/regex/array/struct/query/binary/function/variableName/... Verified against CF 2025 in tests/cfm/tier2_isvalid_test.cfm. |
| IsValidOauthAccesstoken | ❌ No | |||
| IsWDDX | boolean | value: any |
✅ Yes | Reports whether the value is a well-formed WDDX packet: the case-sensitive <wddxPacket> root with a version attribute, a <header/> element and at least one value under <data> (verified against CF 2025 in tests/cfm/tier1_wddx_test.cfm + JitExpressionTest.Tier1IsDdxAndWddx). |
| IsXML | boolean | value: any |
✅ Yes | Checks if string is valid XML or value is XML object |
| IsXmlAttribute | boolean | value: any |
✅ Yes | |
| IsXmlDoc | boolean | value: any |
✅ Yes | |
| IsXmlElem | boolean | value: any |
✅ Yes | |
| IsXmlNode | boolean | value: any |
✅ Yes | |
| IsXmlRoot | boolean | value: any |
✅ Yes | |
| JavaCast | ❌ No | |||
| JSStringFormat | string | string: string |
✅ Yes | Escapes a string for use in JavaScript string literals (\n, \r, \t, \f, \", \', \\) |
| LCase | string | string: string |
✅ Yes | |
| Left | string | string: string, count: numeric |
✅ Yes | |
| Len | numeric | string: string |
✅ Yes | |
| ListAppend | string | list: string, value: string, delimiter: string = "," |
✅ Yes | |
| ListChangeDelims | string | list: string, newDelimiter: string, delimiter: string = "," |
✅ Yes | |
| ListContains | numeric | list: string, value: string, delimiter: string = "," |
✅ Yes | Substring match |
| ListContainsNoCase | numeric | list: string, value: string, delimiter: string = "," |
✅ Yes | Case-insensitive |
| ListDeleteAt | string | list: string, index: numeric, delimiter: string = "," |
✅ Yes | 1-based index |
| ListEach | void | list: string, callback: function [, delimiter: string] [, includeEmptyFields: boolean] |
✅ Yes | Invokes callback(item, index, list) for each list element (index 1-based); returns null. includeEmptyFields keeps empty elements. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce. |
| ListFilter | string | list: string, callback: function [, delimiter: string] [, includeEmptyFields: boolean] |
✅ Yes | Returns a new list string with the elements for which the callback returns true (callback args item, index, list). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce. |
| ListFind | numeric | list: string, value: string, delimiter: string = "," |
✅ Yes | Case-sensitive, 0 if not found |
| ListFindNoCase | numeric | list: string, value: string, delimiter: string = "," |
✅ Yes | Case-insensitive |
| ListFirst | string | list: string, delimiter: string = "," |
✅ Yes | |
| ListGetAt | string | list: string, index: numeric, delimiter: string = "," |
✅ Yes | 1-based index |
| ListGetDuplicates | any | list: string [, delimiter: string] |
✅ Yes | Returns the values that occur more than once, sorted alphabetically and joined with the input delimiter into a string (CF 2021 returns a simple value, not an array). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListGetDuplicatesAndRemove. |
| ListInsertAt | string | list: string, index: numeric, value: string, delimiter: string = "," |
✅ Yes | 1-based index |
| ListLast | string | list: string, delimiter: string = "," |
✅ Yes | |
| ListLen | numeric | list: string, delimiter: string = "," |
✅ Yes | |
| ListMap | string | list: string, callback: function [, delimiter: string] [, includeEmptyFields: boolean] |
✅ Yes | Returns a new list string of the callback results (callback(item, index, list)), joined by the delimiter. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce. |
| ListPrepend | string | list: string, value: string, delimiter: string = "," |
✅ Yes | |
| ListQualify | string | list: string, qualifier: string [, delimiters: string] [, elements: string] [, includeEmptyValues: boolean] |
✅ Yes | Wraps each element in the qualifier (ListQualify("a,b,c","'") → 'a','b','c'). A two-char qualifier is a (prefix, suffix) pair; elements "all"/"char" qualify everything; includeEmptyValues keeps empty elements as ''. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListQualifyAndCounts. |
| ListReduce | any | list: string, callback: function [, initialValue: any] [, delimiter: string] [, includeEmptyFields: boolean] |
✅ Yes | Reduces the list: callback(acc, item, index, list) returns the next accumulator starting from initialValue. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce. |
| ListRemoveDuplicates | string | list: string [, delimiter: string] [, ignoreCase: boolean] |
✅ Yes | Returns the list with duplicate elements removed, keeping the first occurrence (ignoreCase=true makes matching case-insensitive). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListGetDuplicatesAndRemove. |
| ListRest | string | list: string, delimiter: string = "," |
✅ Yes | |
| ListSetAt | string | list: string, index: numeric, value: string, delimiter: string = "," |
✅ Yes | 1-based index |
| ListSort | string | list: string, sortType: string [, sortOrder: string] [, delimiters: string] [, includeEmptyFields: boolean] [, localeSensitive: boolean] or list: string, callback: function |
✅ Yes | Sorts a list (numeric/text/textnocase, asc/desc) or via a comparator callback. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListSortVariants. |
| ListToArray | array | list: string, delimiter: string = "," |
✅ Yes | |
| ListValueCount | numeric | list: string, value: string [, delimiters: string] |
✅ Yes | Case-sensitive count of elements equal to value. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListQualifyAndCounts. |
| ListValueCountNoCase | numeric | list: string, value: string [, delimiters: string] |
✅ Yes | Case-insensitive count of elements equal to value. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListQualifyAndCounts. |
| LJustify | string | string: string, length: numeric |
✅ Yes | Left-justifies a string in a given field length |
| Location | void | url: string, addtoken: boolean, statuscode: numeric |
✅ Yes | ColdFusion 2025 removed the script-accessible Location() function (only the <cflocation> tag remains); calling it throws CF's "Method Location with N arguments is not in class coldfusion.runtime.CFPage." with the actual argument count (verified against CF 2025; covered by JitExpressionTest.Tier1LocationThrowsLikeCf2025). The tag form keeps working via response_redirect. |
| Log | float | number: numeric |
✅ Yes | Natural log |
| Log10 | float | number: numeric |
✅ Yes | Base-10 log |
| LSCurrencyFormat | string | number: numeric [, type: string [, locale: string]] |
✅ Yes | type local (default) / international / none, locale-aware (grouping/decimal separators, symbol position, negative patterns incl. parens for en_US / € 1.234,50- for nl_NL, 0 decimals for JPY/KRW, half-even rounding like Java). |
| LSDateFormat | string | date: any [, mask: string [, locale: string]] |
✅ Yes | Formats with the locale's date mask; a bare mmmm/MMMM mask renders the locale's standalone month form, which for Italian is capitalized (Maggio) while the same token inside a longer mask is lowercase (15 maggio 2020) — a per-locale standaloneMonthCapitalized flag in src/locale_table.inc reproduces CF 2025 (only Italian differs; French/Spanish/Dutch bare months stay lowercase). Verified against CF 2025 in tests/cfm/lsdateformat_bare_month_test.cfm + LocaleTest (was BUGS.md "Italian bare LSDateFormat(d, "mmmm") month-name capitalization"). |
| LSDateTimeFormat | string | date: any [, mask: string [, locale: string]] |
✅ Yes | Locale-aware (default mask dd-MMM-yyyy HH:nn:ss with locale month names; named masks use the locale's date+time patterns). |
| LSEuroCurrencyFormat | string | number: numeric [, type: string [, locale: string]] |
✅ Yes | Euro-zone locales format in Euro; others in their local currency. |
| LSIsCurrency | boolean | string: string [, locale: string] |
✅ Yes | |
| LSIsDate | boolean | string: string [, locale: string] |
✅ Yes | Locale-aware (was BUGS.md #11): parses with the locale's JDK COMPAT patterns + month/day/AM-PM names, the JDK 2-digit-year century window, timezone z/X offsets, and the fixed yyyy-MM-dd/dd.MM.yyyy/dd/MM/yyyy/H:mm:ss.S/... patterns via CFLocaleBase.ParseDateTime. A Date argument → YES; a non-string or all-digits string → NO (CF's isNumberOnly rule); otherwise ParseDateTime succeeds/fails. Byte-verified against CF 2025 in tests/cfm/lsparsedatetime_locale_test.cfm + LocaleTest.LSIsDateUsesLocale. |
| LSIsNumeric | boolean | value: any |
✅ Yes | |
| LSNumberFormat | string | number: numeric [, mask: string [, locale: string]] |
✅ Yes | Default = standard grouping/0 decimals; CF mask language (_, 9,999, 9,999.99, 0000, ...) with locale separators and right-aligned space/zero padding. |
| LSParseCurrency | numeric | string: string [, locale: string] |
✅ Yes | |
| LSParseDateTime | DateTime | string: string [, locale: string] |
✅ Yes | Locale-aware (was BUGS.md #11): implements CF's CFLocaleBase.ParseDateTime — phase-1 datetime formatters (locale short/medium/long/full date+time, RFC1123 EEE, dd MMM yyyy HH:mm:ss zzz, ISO yyyy-MM-dd'T'HH:mm:ssXXX/...ss), then the phase-2 date+time formatters with CF's shared-ParsePosition + errorIndex discard and the date/time remainder split. Patterns come from the per-locale JDK COMPAT data (pDate*/pTime* in src/locale_table.inc, since CF runs java.locale.providers=COMPAT,SPI); month/day/AM-PM names match the locale (COMPAT symbols = the table's existing arrays). JDK 2-digit-year window (now−80y: 15.05.20→2020, 15.05.99→1999, 15.05.70→1970 under German). Explicitly-timezoned strings are reduced to UTC and then localized to the server's timezone (2020-05-15T10:30:00Z on a UTC+2 host → 12:30:00; +05:00/-05:00/RFC1123 GMT handled the same way, with day-boundary rollover via localtime_r; west-of-UTC offsets were previously rejected because the z/X matchers used a negative return both as the offset and as the no-match sentinel — now INT_MIN); a timezone-free string stays a plain server-local wall clock (10:30 → 10:30). Day-of-week conflicts rejected (Tuesday, May 15, 2020), time-only parses onto the OLE epoch 1899-12-30, and a Date argument passes through unchanged. Byte-verified against CF 2025 in tests/cfm/lsparsedatetime_locale_test.cfm (including the day-boundary, negative-offset and RFC1123-GMT cases) + LocaleTest.LSParseDateTime* (was "still parses with the English date parser" in BUGS.md). |
| LSParseEuroCurrency | numeric | string: string [, locale: string] |
✅ Yes | |
| LSParseNumber | numeric | string: string [, locale: string] |
✅ Yes | Parses with the locale's decimal/grouping separators (ASCII spaces accepted as grouping). |
| LSTimeFormat | string | date: any [, mask: string [, locale: string]] |
✅ Yes | Locale-aware (default and named masks use the locale's time patterns incl. AM/PM markers). |
| LTrim | string | string: string |
✅ Yes | |
| Max | numeric | number1: numeric, number2: numeric |
✅ Yes | |
| Mid | string | string: string, start: numeric, count: numeric |
✅ Yes | 1-based start |
| Min | numeric | number1: numeric, number2: numeric |
✅ Yes | |
| Minute | int | date: any |
✅ Yes | |
| Month | int | date: any |
✅ Yes | |
| MonthAsString | string | month_number: int [, locale: string] |
✅ Yes | |
| Now | DateTime | () |
✅ Yes | |
| NumberFormat | string | number: numeric [, mask: string] |
✅ Yes | Full CF mask grammar (grouping, sign/paren/dollar/justify, rounding half-up). Verified against CF 2025 in tests/cfm/tier2_numberformat_test.cfm. |
| ObjectEquals | boolean | clientobject: any, originalobject: any |
✅ Yes | Deep, type-strict equality like CFComparable.objectEquals: values of different CF types are never equal (ObjectEquals(1, "1") and ObjectEquals(1, 1.0) are NO), containers (array/struct/query) are compared recursively. Verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1ObjectEquals. |
| ObjectLoad | any | binaryOrFile: any |
✅ Yes | Round-trips ObjectSave output (JSON wire format with a magic header; NOT Java serialization — documented divergence, see BUGS.md). Verified against CF 2025 for the round-trip in tests/cfm/tier2_objectsave_test.cfm. |
| ObjectSave | binary | obj: any, file: string |
✅ Yes | Serializes to the engine's JSON wire format with a magic header (CF uses Java Object Serialization; documented divergence, see BUGS.md). Verified against CF 2025 for the round-trip in tests/cfm/tier2_objectsave_test.cfm. |
| OnWSAuthenticate | ❌ No | |||
| ORMClearSession | ❌ No | |||
| ORMCloseAllSessions | ❌ No | |||
| ORMCloseSession | ❌ No | |||
| ORMEvictCollection | ❌ No | |||
| ORMEvictEntity | ❌ No | |||
| ORMEvictQueries | ❌ No | |||
| ORMExecuteQuery | ❌ No | |||
| ORMFlush | ❌ No | |||
| ORMFlushall | ❌ No | |||
| ORMGetSession | ❌ No | |||
| ORMGetSessionFactory | ❌ No | |||
| ORMIndex | ❌ No | |||
| ORMIndexPurge | ❌ No | |||
| ORMReload | ❌ No | |||
| ORMSearch | ❌ No | |||
| ORMSearchOffline | ❌ No | |||
| ParagraphFormat | string | string: string |
✅ Yes | |
| ParameterExists | boolean | parameter: string |
✅ Yes | Removed in ColdFusion 2025 (the docs: "available ... until ColdFusion 2025"). Calling it reports "Variable PARAMETEREXISTS is undefined." like CF (verified on the RDS host in tests/cfm/tier1_error_test.cfm); the name was also dropped from the built-in function registry. |
| ParseDateTime | DateTime | date_string: string [, pop: string] |
✅ Yes | CF's US-English DateUtils.parseDateTime (dash/slash/dot separators, year-position disambiguation YYYY-MM-DD/MM-DD-YYYY/DD-MM-YYYY, bare times onto the OLE epoch 1899-12-30, AM/PM). ISO-8601 datetimes are supported (was BUGS.md): the T date/time separator is accepted and a trailing timezone offset (Z/±HH/±HHMM/±HH:MM) is applied by reducing the wall clock to UTC and localizing to the server timezone — 2020-05-15T10:30:00 → 10:30, ...Z → 12:30 on a UTC+2 host, +05:00/-05:00 symmetric (west-of-UTC via the INT_MIN sentinel fix). Because parseDateTimeStr also backs IsDate/IsValid/Year/Hour/.../<cfqueryparam>/<cfparam> date coercion, they accept the same strings. Full consumption is now enforced (was BUGS.md "IsDate()/ParseDateTime() ignore trailing garbage"): the numeric scans track their consumed position (%n) and a trailing non-whitespace tail is rejected (IsDate("2020-05-15bogus") → NO), with fractional seconds (.ddd), trailing offsets and trailing whitespace handled exactly like CF's CFDateTimeParser (see the IsDate row). Byte-verified against CF 2025 in tests/cfm/parsedatetime_iso_t_test.cfm + isdate_trailing_garbage_test.cfm + isdate_fractional_seconds_test.cfm + JitExpressionTest.ParseDateTimeIsoT/IsDateRejectsTrailingGarbage. |
| Pi | float | () |
✅ Yes | 3.141592653589793 |
| PrecisionEvaluate | numeric | expressions: string |
✅ Yes | Evaluates the expression string(s) with Java BigDecimal-style arbitrary-precision decimal arithmetic. The expression is parsed with the engine's real CFML expression parser: the argument is wrapped in component { <expr> } (a .cfc script component), tokenized with textparser, turned into an ExprAST via parseTokensToAST, and evaluated by a BigDecimal-aware AST walker (src/codegen/fn_precisionevaluate.cpp, in the compiler library where textparser + the parser live; both the JIT and the interpreter dispatch to it). The decimal arithmetic is a self-contained engine (sign + digit string + scale, chosen over decNumber because BigDecimal's scale-based semantics differ from IEEE-754 decimal). + - * are exact (add keeps max(a.scale,b.scale), multiply keeps a.scale+b.scale, operands parse like Cast._BigDecimal); / reproduces CF's _divideBD: first a.divide(b, ROUND_UNNECESSARY) (result scale = a.scale; succeeds only when the exact quotient needs no more fractional digits than a.scale), else scale 20 HALF_EVEN; ^ reproduces _powBD: new BigDecimal(Math.pow(a.doubleValue(), b.doubleValue())) — double math converted to the exact decimal expansion of the double; MOD/% and \ are integer ops (operands truncated toward zero); comparisons support ==/!= and the word forms EQ/NEQ/IS/IS NOT/LT/LTE/GT/GTE/LE/GE/CONTAINS/DOES NOT CONTAIN/NOT (CF's precision grammar rejects the symbolic </>/<=/>=, reproduced by scanning the operator tokens). Results render with BigDecimal.toString() (plain vs E notation; e.g. 1/10000000 → 1.0000000000000E-7); a string argument containing a comma is split into expressions (the last wins); true/false/bare identifiers/plain numbers pass through like CF's PrecisionEvalExprClassLoader. Identifiers inside the expression (and the whole-expression fast path) resolve through the scopes like CF's _autoscalarize — including dotted scope names (form.x), and numeric string variables coerce like Cast._BigDecimal. Division by zero throws "Division by zero.". Verified byte-for-byte against CF 2025 in tests/cfm/tier2_precisionevaluate_test.cfm + JitExpressionTest.Tier2PrecisionEvaluate* unit tests. |
| PreserveSingleQuotes | void | variable: string |
✅ Yes | CF 2025 no longer auto-escapes single quotes in query literals, so the function is an identity: the argument passes through unchanged (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm). |
| ProcessSAMLLogoutRequest | ❌ No | |||
| ProcessSAMLResponse | ❌ No | |||
| Quarter | int | date: any |
✅ Yes | |
| QueryAddColumn | numeric | query: query, column_name: string [, datatype: string], array_name: array |
✅ Yes | Appends a column to a query and fills it from a one-dimensional array. Returns the new column count. Type coercion applies per datatype (default varchar). Existing columns are padded to the new column's length when the array is longer, and a shorter array is padded with nulls to the query's row count (verified against CF 2021). Duplicate column name and non-array last argument throw. Verified in tests/cfm/queryaddcolumn1.cfm + QueryMutationTest. |
| QueryAddRow | numeric | `query: query [, number: numeric | row: struct | rows: array]` |
| QueryConvertForGrid | struct | query: query, page: numeric, pageSize: numeric |
✅ Yes | Returns {QUERY: pagedSubset, TOTALROWCOUNT: totalRows}. The subset holds the rows for page/pageSize (1-based), clamped to the record count — page beyond the last row yields an empty query, pageSize=0 yields an empty query, a negative start index (page-1)*pageSize throws (CF: Java IndexOutOfBounds). Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryConvertForGrid*. |
| QueryEach | void | query: query, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Invokes callback(row, currentRow, query) for each row (row is a fresh struct of the row's cells; currentRow is 1-based; the query argument is a copy). Returns null (CF: assigning the void return leaves the variable undefined). parallel/maxThreads are accepted and ignored (sequential execution). Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryEach*. |
| QueryExecute | query | sql: string [, params: any [, options: struct]] |
✅ Yes | Executes SQL with positional (?) or named (:name) parameter substitution (list params, VALUE/CFSQLTYPE/NULL structs supported) and cfquery options. Implemented on the shared cf_run_query executor (extracted from <cfquery>). options.dbtype="query" runs a Query of Queries (the referenced query objects are resolved from the caller's scopes and executed in-memory via SQLite — see the cfquery row); the other options map to the same cf_run_query attributes. Verified against CF 2025 in tests/cfm/tier2_queryexecute_test.cfm (DB-backed) + tests/cfm/query_of_queries_test.cfm section T13 (QoQ) + CfQueryTest.QueryOfQueries* unit tests. |
| QueryFilter | query | query: query, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Returns a NEW query with the rows for which the callback returns true (original unchanged, column names/types preserved). Callback gets (row, currentRow, query). parallel/maxThreads accepted and ignored. Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryFilter*. |
| QueryGetResult | struct | query: query |
✅ Yes | Returns the cached <cfquery> resultset metadata struct; a QueryNew-built query has none, so CF returns null (verified vs CF 2021: IsNull(QueryGetResult(q)) → YES, cfdump renders "undefined"). All queries in this engine come from QueryNew, so null is the faithful result. Verified in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryGetResultIsNull. |
| QueryGetRow | struct | query: query, rowNumber: numeric |
✅ Yes | Returns a struct with the query columns as keys and the row's cell values (original column-name casing preserved, case-insensitive lookup). Out-of-bounds row numbers throw (CF: Expression). Verified in tests/cfm/querygetrow1.cfm + QueryMutationTest. |
| QueryKeyExists | boolean | query: query, key: string |
✅ Yes | Returns YES/NO depending on whether the named column exists in the query (case-insensitive; empty key → NO). Verified in tests/cfm/querykeyexists1.cfm + QueryMutationTest. |
| QueryMap | query | query: query, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Returns a NEW query (same columns/types) whose rows are the structs returned by callback(row, currentRow, query). The callback may mutate the passed row and return it, or return a new struct with exactly the query's columns (a missing/extra column throws, matching CF's fatal error; a non-struct return throws). Mapped cells keep their raw values. parallel/maxThreads accepted and ignored. Note: arithmetic on typed (integer) cells produces Integer here vs Double in CF, so mapped integer arithmetic renders 10 vs CF's 10.0 — a pre-existing arithmetic-type divergence, not a QueryMap bug. Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryMap*. |
| QueryNew | query | columnList: string/struct [, columnTypeList: string] [, rowData: array/struct] |
✅ Yes | Creates a query value with type coercion per column. columnlist returns the sorted uppercased names; recordcount/currentrow pseudo-properties; q.col/q["col"] return the column as an array (so q.col[1] reads a cell); dot access resolves pseudo-properties first, bracket access resolves columns first (verified against CF 2021). rowData forms: single struct (one row), array of structs (extra keys throw — CF Expression error), array of arrays (positional), scalar (one row, first column). A struct first argument is row data, not a name→type map (QueryNew({a:"integer",b:"varchar"}) → columns A,B with a 1-row query whose cells are the values, all varchar). Column types default to varchar; integer truncates decimals ("30.9"→30) and rejects non-numeric ("Invalid data ... for CFSQLTYPE CF_SQL_INTEGER."), bigint→Long, double/decimal/numeric/float→Float, date/time/timestamp→DateTime, boolean/bit→Boolean. SerializeJSON → {"COLUMNS":[...],"DATA":[...]} with original order (uppercased names), typed cells (numbers as JSON numbers, dates as "January, 01 2020 00:00:00", null for missing cells). cfdump (HTML + text) byte-identical to CF 2021. Verified byte-for-byte in tests/cfm/querynew1-4.cfm + QueryNewTest unit tests. Query-column copy semantics match CF 2021/2025 (was BUGS.md): q["a"][1] = "z" and q.a[1] = "z" cell writes persist through a query-column back-reference; a stored reference is live (x = q["a"]; x[1] = "MUT" writes through and q["a"][2] = "YY" shows through x[2]), not a real array (ArrayLen/ArrayAppend reject it with CF's "cannot be used as an array" error, IsArray is NO, stringifies as its scalar first cell), a dot-origin copy or a second copy (y = x) degrades to the scalar first cell so element writes throw CF's "dereference a scalar variable of type class java.X as a structure with members." Expression error, and a write past the last row is invisible. The QueryData is refcounted from column references (reassigning q does not dangle a copy). Whole-column assignment matches CF: q.a = v writes the current row's cell (coerced to the column type), a non-column name throws CF's "columnMap" Application error, and q["a"] = v throws CF's "trying to modify the query named class coldfusion.sql.QueryTable." Expression error (an array RHS is rejected with the same error; CF's own array-to-column behavior is erratic — see BUGS_CF.md). Verified byte-for-byte in tests/cfm/querycell_assign.cfm + query_column_copy_test.cfm + query_column_assign_test.cfm + QueryMutationTest.CellAssign*/CopiedColumnWritesThrough/CopiedDotColumnIsReadOnly/SecondCopyOfBracketColumnThrows/SecondCopyOfDotColumnThrows/StoredColumnRefIsNotAnArray/CopiedColumnReadsLive/PhantomRowWriteInvisible/DegradedColumnCopyIsScalar/DegradedIntegerColumnCopyIsScalar/DotColumnFirstCopyIsScalar/WholeColumnAssign*/BracketWholeColumnAssignThrows. |
| QueryReduce | any | query: query, callback: function [, initialValue: any] |
✅ Yes | Reduces the query rows: callback(acc, row, currentRow, query) returns the next accumulator, starting from initialValue (without it the accumulator starts null — CF throws "Variable ACC is undefined." on the first use). An empty query returns the initial value unchanged. Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryReduce*. |
| QuerySetCell | boolean | query: query, column: string, value: any [, row: numeric] |
✅ Yes | Sets a cell to a value and returns YES. Without a row number the last row is used; an explicit row must be in bounds (0 and out-of-bounds positive rows throw — CF Expression; negative row numbers behave like the last row). Setting a cell on a query with no rows throws. Values are coerced to the column's type (e.g. integer column: "30.9"→30, invalid → Database error). currentrow is not advanced. Verified in tests/cfm/querysetcell1.cfm + QueryMutationTest. |
| QuotedValueList | string | column: query column [, delimiter: string = ","] |
✅ Yes | Returns the values of a query column, each wrapped in single quotes ('a','b') and joined by the delimiter. Embedded quotes are NOT escaped (CF behavior); empty string values become ''; an empty column returns "". Passing the whole query (no column) throws (CF rejects it). Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QuotedValueList. |
| Rand | float | [algorithm: string] |
✅ Yes | [0, 1). The optional algorithm (default SHA1PRNG) is validated per-thread like CF: an unsupported name throws CF's "The X algorithm is not supported by the Security Provider you have chosen." only on the first Rand/RandRange call of a thread (CF caches its SecureRandom generator in a never-cleared ThreadLocal); later calls reuse the cache and silently ignore a bad name — the cache is also populated by Randomize/Rand/RandRange, so the error is order-dependent and non-deterministic on a pooled server, exactly like CF. Verification: rand_algorithm_caching_test.cfm (byte-verified against CF 2025) + JitExpressionTest.RandRange*/RandRandomizeAlgorithmCaching. |
| Randomize | numeric | seed: numeric [, algorithm: string] |
✅ Yes | Seeds RNG and returns the next value of the freshly reseeded generator — a numeric in [0,1), deterministic for a given seed (Randomize(42) twice yields the same value, and the following Rand()/RandRange() continues the seeded sequence), matching CF's random.nextDouble() (CF: #Randomize(42)# → 0.832195441766, IsNumeric → YES). Unlike Rand/RandRange, Randomize ALWAYS validates the algorithm (its 2-arg generator path calls SecureRandom.getInstance and reseeds every call, never consulting the per-thread cache) and overwrites the cache with the freshly seeded generator. An unsupported name throws CF's message on every call. |
| RandRange | numeric | number1: numeric, number2: numeric [, algorithm: string] |
✅ Yes | Generates a random integer in [min, max] inclusive, swapping the bounds when number1 > number2 (CF behavior, verified: RandRange(5,3) yields values in [3,5] and RandRange(7,5) in [5,7]). Equal bounds always return that value. The optional algorithm argument (default SHA1PRNG) is validated per-thread like CF (see Rand): the name is matched case-insensitively with no trimming (empty/whitespace-padded names throw on a fresh thread, lowercase sha1prng works), and once any Rand/RandRange/Randomize call has populated the thread's cache, later calls silently ignore any algorithm name. Valid names: SHA1PRNG, NativePRNG, NativePRNGNonBlocking, NativePRNGBlocking, DRBG, Windows-PRNG. The randomness comes from the same rand() generator as Rand/Randomize. Verified in tests/cfm/randrange_test.cfm + rand_algorithm_caching_test.cfm + JitExpressionTest.RandRange*. |
| ReEscape | string | string: string |
✅ Yes | Escapes regex control characters in string (the set |$^+?{}*.[]()\&- from StringFunc's ESCAPE_REGEX_PATTERN; note & and - are included). Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitReEscape. |
| REFind | any | reg_expression: string, string: string, start: numeric = 1, returnsubexpressions: boolean = false, scope: string = "one" |
✅ Yes | Case-sensitive regex search (Apache ORO Perl5 semantics via a PCRE2 backend). Returns the 1-based start position of the first match (0 if none), or with returnsubexpressions=true a struct {MATCH[], POS[], LEN[]} (POS is 1-based; a non-participating group reports ""/0/0); no match returns {MATCH:[""],POS:[0],LEN:[0]}. scope="all" (CF2016+) returns an array of positions/structs. . matches \n (CF compiles ORO patterns with SINGLELINE). ORO-unsupported constructs are rejected with CF's errors: lookbehind/named groups (Sequence (?<...) not recognized), atomic (?> (Sequence (?>...) not recognized), scoped modifiers (?i: (Sequence (?:...) not recognized); escapes ORO treats as bare letters (\u \U \l \L \F \v \p \P \N \R \X \K \Q \E) are normalized. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.*. |
| REFindNoCase | any | reg_expression: string, string: string, start: numeric = 1, returnsubexpressions: boolean = false, scope: string = "one" |
✅ Yes | Case-insensitive REFind (PCRE2_CASELESS). Same return shapes and ORO compatibility as REFind. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.*. |
| ReleaseComObject | ❌ No | |||
| REMatch | array | reg_expression: string, string: string |
✅ Yes | Returns an array of every substring matched by reg_expression (group 0 of each match), empty array if none. Zero-length matches are collected with the ORO/Java find() advancement rule. . matches \n; ORO syntax restrictions apply as in REFind. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitRematch. |
| REMatchNoCase | array | reg_expression: string, string: string |
✅ Yes | Case-insensitive REMatch. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitRematch. |
| RemoveCachedQuery | void | SQL: string, datasource: string [, params] [, region] |
✅ Yes | Removes a query from the query cache (QUERY region, or the region arg). <cfquery cachedwithin>/cachedafter/cacheid/cacheregion now store freshly-executed results in the same cache (key = explicit cacheid, else CF's 29*(29*dsnHash+sqlHash)+paramHash), serve hits with result.CACHED="true", and RemoveCachedQuery deletes by the same hash key. Not yet byte-verified against CF 2025 (the CF caching package is installed on the RDS host but the Cache* functions still diverge — see BUGS_CF.md); covered by CacheTest unit tests + the query-cache CLI checks. |
| RemoveChars | string | string: string, start: numeric, count: numeric |
✅ Yes | Removes count characters starting at 1-based start |
| RepeatString | string | string: string, count: numeric |
✅ Yes | |
| Replace | string | string: string, substring1: string, substring2: string, scope: string = "ONE" |
✅ Yes | |
| ReplaceList | string | string: string, list1: string, list2: string [, delim1: string] [, delim2: string] [, includeEmptyFields: boolean] |
✅ Yes | Replaces occurrences of each list1 element with the paired list2 element, applied pair by pair, sequentially, ALL occurrences, case-sensitive (port of StringFunc.ReplaceList + CFPage arg handling). A 4th/5th argument of true/yes/false/no selects includeEmptyFields; otherwise they are the delimiters. Delimiters follow escapeDelim: multi-char = char set, regex metachars escaped (./` |
| ReplaceNoCase | string | string: string, substring1: string, substring2: string, scope: string = "ONE" |
✅ Yes | |
| REReplace | string | string: string, reg_expression: string, substring: string, scope: string = "one" |
✅ Yes | Replaces the first (scope ONE, default) or every (scope ALL) match of reg_expression in string. Substitution follows CF's ORO pre-processing: \1.. back-references, literal $ (write \$), literal backslashes pass through, and the ORO case-conversion controls \U/\L (to \E) and \u/\l (first char of next element). A reference to a nonexistent/non-participating group renders empty; multi-digit refs (e.g. \10 = group 10) are parsed like ORO. Empty regex throws Argument 2 of function REReplace cannot be an empty value.; an invalid scope throws The scope argument of the REReplace function has an invalid value X. (CF hardcodes "REReplace" here even for REReplaceNoCase). Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm, refind_re_errors_test.cfm + RegexFunctionsTest.*. |
| REReplaceNoCase | string | string: string, reg_expression: string, substring: string, scope: string = "one" |
✅ Yes | Case-insensitive REReplace (PCRE2_CASELESS). Same substitution semantics as REReplace. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitRereplace. |
| RestDeleteApplication | ❌ No | |||
| RestInitApplication | ❌ No | |||
| RestSetResponse | ❌ No | |||
| Reverse | string | string: string |
✅ Yes | |
| Right | string | string: string, count: numeric |
✅ Yes | |
| RJustify | string | string: string, length: numeric |
✅ Yes | Right-justifies a string in a given field length |
| Round | numeric | number: numeric |
✅ Yes | |
| RTrim | string | string: string |
✅ Yes | |
| Second | int | date: any |
✅ Yes | |
| SendGatewayMessage | ❌ No | |||
| SendSAMLLogoutResponse | ❌ No | |||
| Serialize | string | value: any, [type: string] |
✅ Yes | Wrapper, delegates to SerializeJSON for vars |
| SerializeJSON | string | value: any |
✅ Yes | Compact output (no spaces) matching CF: struct keys emitted with their original casing in ColdFusion's Java-HashMap iteration order (hash bucket of the upper-cased key, insertion order within a bucket, capacity grows 16→32→64…); literal floats keep their text (8.0); computed doubles render like Java Double.toString (1/3 → 0.3333333333333333, 2^53 → 9.007199254740992E15, 10^9 → 1.0E9, 10^-5 → 1.0E-5); Long values serialize as int64 numbers (SerializeJSON(DeserializeJSON("9007199254740993")) → 9007199254740993). Known divergence: CF quotes integer literals beyond int32 (SerializeJSON(9223372036854775807) → "9223372036854775807", a CFBigInteger artifact) while this engine emits them as numbers. Verified against CF 2021 in tests/cfm/json1.cfm, json_struct_order_test.cfm, json_computed_float_test.cfm, tests/cfm/long_type_test.cfm (previously BUGS.md #1) |
| SerializeXML | string | xmlNode: xml |
✅ Yes | |
| SessionGetMetaData | struct | () |
✅ Yes | Returns {STARTTIME: <creation date>} for the current session (the date rendered like CF: August, 04 2026 05:34:21). Verified against CF 2025 in tests/cfm/cfapplication_scope_test.cfm. |
| SessionInvalidate | void | () |
✅ Yes | Clears the session data and deletes the session row, making the current CFID/CFTOKEN invalid; the (now-invalid) cookies stay in the response and the next request mints a fresh session. Verified against CF 2025 in tests/cfm/cfsession_invalidate_test.cfm. |
| SessionRotate | void | () |
✅ Yes | Mints a new CFID/CFTOKEN pair (new cookies), copies the session data into the new session row and removes the old one (anti-session-fixation). Verified against CF 2025 in tests/cfm/cfsession_rotate_test.cfm. |
| SetDay | DateTime | date: any, day: int |
✅ Yes | Member method date.setDay(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm. |
| SetEncoding | void | scope_name: string, charset: string |
✅ Yes | Sets the character encoding of the FORM or URL scope variables; throws "Only form or URL scope is allowed in a SetEncoding() call." for any other scope. The encoding is tracked per request and reflected by GetEncoding (verified against CF 2025 in tests/cfm/tier1_setencoding_test.cfm). |
| SetHour | DateTime | date: any, hour: int |
✅ Yes | Member method date.setHour(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm. |
| SetLocale | string | new_locale: string |
✅ Yes | Sets the current request's locale (28 CF named locales + java locale codes like fr_FR, case-insensitive), returns the previous locale's CF string, sets the Content-Language response header (en-US, ...), and drives GetLocale + all LS* formatting/parsing. An unknown locale throws CF's The locale, X, cannot be found. The locale table (src/locale_table.inc) holds month/day names, short/medium/long/full date+time masks and number/currency data for the 28 CF locales, probed byte-for-byte from the CF 2025 RDS host (old-JVM CLDR, e.g. German short day Fr, Italian time 14.30, NBSP group separators). Verified byte-for-byte against CF 2025 in tests/cfm/setlocale_test.cfm + LocaleTest unit tests. An empty-string locale argument resolves to the English (US) default like CF's CFLocaleMgr (which registers "" → en_US) instead of throwing; whitespace/unknown names still throw. The table also carries the JDK COMPAT parse patterns (pDate*/pTime*) that back the now locale-aware LSParseDateTime/LSIsDate (was BUGS.md #11). |
| SetMinute | DateTime | date: any, minute: int |
✅ Yes | Member method date.setMinute(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm. |
| SetMonth | DateTime | date: any, month: int |
✅ Yes | Member method date.setMonth(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm. |
| SetProfileString | string | iniPath: string, section: string, key: string, value: string |
✅ Yes | Rewrites an INI section/key preserving comment/key casing and section order; returns "". Verified against CF 2025 in tests/cfm/tier2_setprofile_test.cfm. |
| SetPropertyString | void | name: string, key: string, value: string (or name, struct) |
✅ Yes | Writes a Java .properties file (dict form accepts a struct). RDS host permission-rejects the write; implemented in fn_getpropertyfile.cpp. |
| SetSecond | DateTime | date: any, second: int |
✅ Yes | Member method date.setSecond(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm. |
| SetVariable | void | name: string, value: any |
✅ Yes | Assigns the named variable (dotted paths auto-create nested structs, scope-qualified names like form.x work) and returns the assigned value like CF 2025 (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1SetVariable). Reuses cfvariant_assign, which now auto-creates an unknown dotted prefix in the variables scope instead of dropping the first segment. |
| SetYear | DateTime | date: any, year: int |
✅ Yes | Member method date.setYear(n) supported. Mutates in place and returns the modified date. Bare SetYear/SetMonth/SetDay/SetHour/SetMinute/SetSecond calls inside component methods now use component-method precedence before built-in dispatch, while page-level calls retain the two-argument built-in behavior. Verified in tests/cfm/component_bare_date_method_test.cfm and ComponentTest.BareDateMutatorNameResolvesComponentMethod. |
| Sgn | numeric | number: numeric |
✅ Yes | Returns -1, 0, or 1 |
| Sin | float | number: numeric |
✅ Yes | |
| Sleep | void | duration: numeric |
✅ Yes | Pauses current thread execution for specified milliseconds |
| SpanExcluding | string | string: string, set: string |
✅ Yes | Returns the leading run of characters not in set |
| SpanIncluding | string | string: string, set: string |
✅ Yes | Returns the leading run of characters in set |
| SpreadsheetAddAutoFilter | ❌ No | |||
| SpreadsheetAddColumn | ❌ No | |||
| SpreadsheetAddFreezePane | ❌ No | |||
| SpreadsheetAddImage | ❌ No | |||
| SpreadsheetAddInfo | ❌ No | |||
| SpreadsheetAddPageBreaks | ❌ No | |||
| SpreadsheetAddPrintGridlines | ❌ No | |||
| SpreadsheetAddRow | ❌ No | |||
| SpreadsheetAddRows | ❌ No | |||
| SpreadsheetAddSplitPane | ❌ No | |||
| SpreadsheetCreateSheet | ❌ No | |||
| SpreadsheetDeleteColumn | ❌ No | |||
| SpreadsheetDeleteColumns | ❌ No | |||
| SpreadsheetDeleteRow | ❌ No | |||
| SpreadsheetDeleteRows | ❌ No | |||
| SpreadsheetFormatCell | ❌ No | |||
| SpreadsheetFormatCellRange | ❌ No | |||
| SpreadsheetFormatColumn | ❌ No | |||
| SpreadsheetFormatColumns | ❌ No | |||
| SpreadsheetFormatRow | ❌ No | |||
| SpreadsheetFormatRows | ❌ No | |||
| SpreadsheetGetCellComment | ❌ No | |||
| SpreadsheetGetCellFormula | ❌ No | |||
| SpreadsheetGetCellValue | ❌ No | |||
| SpreadsheetGetColumnCount | ❌ No | |||
| SpreadsheetGetColumnWidth | ❌ No | |||
| SpreadsheetGetLastRowNumber | ❌ No | |||
| SpreadsheetGetPrintOrientation | ❌ No | |||
| SpreadsheetGroupColumns | ❌ No | |||
| SpreadsheetGroupRows | ❌ No | |||
| SpreadsheetInfo | ❌ No | |||
| SpreadsheetisBinaryFormat | ❌ No | |||
| SpreadsheetisColumnHidden | ❌ No | |||
| SpreadsheetisRowHidden | ❌ No | |||
| SpreadsheetisStreamingXmlFormat | ❌ No | |||
| SpreadsheetisXmlFormat | ❌ No | |||
| SpreadsheetMergeCells | ❌ No | |||
| SpreadsheetNew | ❌ No | |||
| SpreadsheetRead | ❌ No | |||
| SpreadsheetReadBinary | ❌ No | |||
| SpreadsheetRemoveColumnBreak | ❌ No | |||
| SpreadsheetRemovePrintGridlines | ❌ No | |||
| SpreadsheetRemoveRowBreak | ❌ No | |||
| SpreadsheetRemoveSheet | ❌ No | |||
| SpreadsheetRemoveSheetNumber | ❌ No | |||
| SpreadsheetRenameSheet | ❌ No | |||
| SpreadsheetSetActiveSheet | ❌ No | |||
| SpreadsheetSetActiveSheetNumber | ❌ No | |||
| SpreadsheetSetCellComment | ❌ No | |||
| SpreadsheetSetCellFormula | ❌ No | |||
| SpreadsheetSetCellValue | ❌ No | |||
| SpreadsheetSetColumnBreak | ❌ No | |||
| SpreadsheetSetColumnHidden | ❌ No | |||
| SpreadsheetSetColumnWidth | ❌ No | |||
| SpreadsheetSetFittoPage | ❌ No | |||
| SpreadsheetSetFooter | ❌ No | |||
| SpreadsheetSetFooterImage | ❌ No | |||
| SpreadsheetSetHeader | ❌ No | |||
| SpreadsheetSetHeaderImage | ❌ No | |||
| SpreadsheetSetRowBreak | ❌ No | |||
| SpreadsheetSetRowHeight | ❌ No | |||
| SpreadsheetSetRowHidden | ❌ No | |||
| SpreadsheetShiftColumns | ❌ No | |||
| SpreadsheetShiftRows | ❌ No | |||
| SpreadsheetUngroupColumns | ❌ No | |||
| SpreadsheetUngroupRows | ❌ No | |||
| SpreadsheetWrite | ❌ No | |||
| Sqr | float | number: numeric |
✅ Yes | Square root |
| StoreAddACL | ❌ No | |||
| StoreGetACL | ❌ No | |||
| StoreGetMetadata | ❌ No | |||
| StoreSetACL | ❌ No | |||
| StoreSetMetadata | ❌ No | |||
| StreamingSpreadsheetCleanup | ❌ No | |||
| StreamingSpreadsheetIsStreamingXmlFormat | ❌ No | Stub present: throws Function StreamingSpreadsheetIsStreamingXmlFormat is not implemented |
||
| StreamingSpreadsheetIsXmlFormat | ❌ No | Stub present: throws Function StreamingSpreadsheetIsXmlFormat is not implemented |
||
| StreamingSpreadsheetNew | ❌ No | |||
| StreamingSpreadsheetProcess | ❌ No | |||
| StreamingSpreadsheetRead | ❌ No | |||
| StripCR | string | string: string |
✅ Yes | Removes all carriage-return \r characters, keeps \n |
| StructAppend | boolean | destStruct: struct, sourceStruct: struct [, overwriteFlag: boolean = true] |
✅ Yes | Merges source into dest (default overwrites existing keys), returns YES. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructAppendAndCopy. |
| StructClear | boolean | struct: struct |
✅ Yes | Clears struct in-place |
| StructCopy | struct | structure: struct |
✅ Yes | Returns an independent deep copy of the struct. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructAppendAndCopy. |
| StructCount | numeric | struct: struct |
✅ Yes | |
| StructDelete | boolean | struct: struct, key: string, indicateExisting: boolean = false |
✅ Yes | Also accepts component this scopes as struct-compatible values, matching Adobe CF; verified in tests/cfm/component_structdelete_test.cfm and ComponentTest.StructDeleteAcceptsComponentThisScope. |
| StructEach | void | struct: struct, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Invokes callback(key, value, struct) for each entry in insertion order; returns null. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructEachFilterMapReduce. |
| StructFilter | struct | struct: struct, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Returns a new struct with the entries for which the callback returns true (callback args key, value, struct). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm. |
| StructFind | any | struct: struct, key: string |
✅ Yes | |
| StructFindKey | array | top: any, value: string [, scope: string = "one"] |
✅ Yes | Recursively searches nested structs/arrays for a matching key; returns an array of {path, owner, value}. scope="all" returns every match. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructFindKeyAndValue. |
| StructFindValue | array | top: any, value: any [, scope: string = "one"] |
✅ Yes | Recursively searches for struct values equal to value; returns an array of {key, path, owner}. scope="all" returns every match. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructFindKeyAndValue. |
| StructGet | struct | path: string |
✅ Yes | Resolves (creating as needed) nested structs along a dotted path in the variables scope and returns the last struct. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructGetCreatesPath. |
| StructGetMetadata | struct | struct: struct |
✅ Yes | Returns {ordered, casesensistive, [keys]} — the inherent properties ("unordered"/"NO" for a regular struct) plus the metadata set by StructSetMetadata under keys. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructGetAndSetMetadata. |
| StructInsert | boolean | struct: struct, key: string, value: any, allowOverwrite: boolean = false |
✅ Yes | |
| StructIsEmpty | boolean | struct: struct |
✅ Yes | |
| StructKeyArray | array | struct: struct |
✅ Yes | |
| StructKeyExists | boolean | struct: struct, key: string |
✅ Yes | |
| StructKeyList | string | struct: struct, delimiter: string = "," |
✅ Yes | |
| StructMap | struct | struct: struct, callback: function [, parallel: boolean] [, maxThreads: numeric] |
✅ Yes | Returns a new struct with each value replaced by callback(key, value, struct). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructEachFilterMapReduce. |
| StructNew | struct | () |
✅ Yes | 0 args |
| StructReduce | any | struct: struct, callback: function [, initialValue: any] |
✅ Yes | Reduces the struct: callback(acc, key, value, struct) returns the next accumulator starting from initialValue. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructEachFilterMapReduce. |
| StructSetMetadata | void | inputStruct: struct, metaStruct: struct |
✅ Yes | Attaches metadata to a struct, surfaced by StructGetMetadata under keys; returns null. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructGetAndSetMetadata. |
| StructSort | array | struct: struct [, sortType: string] [, sortOrder: string] [, path: string] [, localeSensitive: boolean] or struct: struct, callback: function |
✅ Yes | Returns the struct's keys (uppercased) sorted by their VALUES (numeric/text/textnocase, asc/desc) or by a comparator callback that receives the values. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructSortAndToSorted. |
| StructToSorted | struct | structure: struct, callback: function or structure: struct, sortType: string [, sortOrder: string] [, localeSensitive: boolean] |
✅ Yes | Returns a NEW struct with its keys reordered — the text/desc forms sort by key, the callback form sorts by value (matching CF 2021). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructSortAndToSorted. |
| StructUpdate | boolean | struct: struct, key: string, value: any |
✅ Yes | |
| StructValueArray | array | struct: struct |
✅ Yes | Returns values of the structure as an array |
| Tan | float | number: numeric |
✅ Yes | |
| ThreadJoin | ❌ No | |||
| ThreadTerminate | ❌ No | |||
| Throw | — | [message] [, type] [, detail] [, errorcode] [, extendedinfo] |
✅ Yes | Throws a developer exception (statement form; positional/named attributes, bare throw rethrows). Verified against CF 2025 in tests/cfm/tier2_throw_test.cfm. |
| TimeFormat | string | time: any [, mask: string] |
✅ Yes | |
| ToBase64 | string | string_or_object: any, encoding: string = "UTF-8" |
✅ Yes | Accepts string or binary; standard base64 with padding |
| ToBinary | binary | base64_or_object: any |
✅ Yes | Base64-decodes to binary; accepts binary and returns it unchanged |
| ToScript | string | cfvar: any, javascriptvar: string [, outputformat: boolean = true] [, asformat: boolean = false] |
✅ Yes | Emits one name = value; line per assignment; strings escaped (\\, \', \", \n, \r, \t); arrays/structs/date recursively expanded (month is 0-based); struct keys lowercased; float literal text preserved (ToScript(8.0) → j = 8.0;); outputformat/asformat accepted but ignored (CF 2021 probe shows identical output) |
| ToString | string | value: any [, encoding: string = "UTF-8"] |
✅ Yes | String/Number/Float/Boolean/DateTime pass through; XML objects serialize to XML string (including <?xml version="1.0" encoding="UTF-8"?> header for documents/elements); binary decoded with charset (default UTF-8); other complex objects throw; float literals keep their literal text (ToString(8.0) → 8.0); computed floats still use shortest-double (to_chars general), not CF's %.12g, except NaN which renders as the single U+FFFD character like CF (ToString((-2)^0.5) → U+FFFD, Len()==1); booleans follow the literal/computed distinction — ToString(true) → true, ToString((5 GT 3)) → YES |
| Trace | void | [var] [, text] [, type] [, category] [, inline] [, abort] |
✅ Yes | No-op when debugging is disabled (the RDS host default and this engine, which has no debug section); abort does not fire. Verified against CF 2025 in tests/cfm/tier2_trace_test.cfm. |
| TransactionCommit | void | () |
✅ Yes | Commits the current <cftransaction> (delegates to the transaction runtime); outside an active transaction throws CF's "An active transaction is required before calling any transaction method." (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1TransactionFunctions). |
| TransactionRollback | void | savepoint: string |
✅ Yes | Rolls back the current <cftransaction>; with a savepoint name rolls back to that savepoint (TransactionSetSavePoint), without one ends the whole transaction. Outside an active transaction throws CF's active-transaction error; an unknown savepoint throws CF's "Specified savepoint does not exist or is invalid." Verified against CF 2025 in tests/cfm/tier1_error_test.cfm + tier2_transactionsetsavepoint_test.cfm + JitExpressionTest.Tier1TransactionFunctions. |
| TransactionSetSavePoint | void | savepoint: string |
✅ Yes | Names a savepoint in the active transaction (sqlite SAVEPOINT); transactionRollback(name) undoes everything after it. Outside a transaction throws CF's active-transaction error. Verified against CF 2025 in tests/cfm/tier2_transactionsetsavepoint_test.cfm. |
| Trim | string | string: string |
✅ Yes | |
| UCase | string | string: string |
✅ Yes | |
| URLDecode | string | urlencodedstring: string [, charset: string = "UTF-8"] |
✅ Yes | '+' becomes space; %XX (case-insensitive) decoded as bytes; UTF-8 by default with U+FFFD replacement for invalid sequences; malformed % passes through literally |
| URLEncodedFormat | string | String: string [, charset: string = "UTF-8"] |
✅ Yes | Percent-encodes everything except ASCII alphanumerics (space becomes %20); charset argument respected |
| URLSessionFormat | string | url: string |
✅ Yes | Appends ?CFID=..&CFTOKEN=.. (or & when the URL already has a query string) when a fresh session was minted in the current request; a request that carried a valid session renders the URL unchanged (verified on CF 2025). The appended CFID value differs per server run by design. |
| Val | numeric | str: string |
✅ Yes | Manual scan [ws][sign]digits[.digits]; no exponent ("1e3"→1); integral results return Number, fractional return Float; Boolean→0; >INT32 range→Float |
| ValueList | string | column: query.column [, delimiter: string = ","] |
✅ Yes | Returns a delimited list of a query column's values. ValueList(q.a) receives the column as an array (our q.a member access returns the column array). Null cells contribute an empty item, keeping the delimiter ([null,"z"] → ",z", verified against CF 2021). An empty query returns an empty string. A non-column argument throws. Verified in tests/cfm/valuelist1.cfm + QueryMutationTest. |
| VerifyClient | void | () |
✅ Yes | Validates client request against _cf_clientid in URL or FORM scope. When session management is enabled, checks client ID against uppercase MD5 hash of urltoken. Throws ApplicationException ("Client verification failure.", detail: "You must have a valid login to access this page.") if missing or mismatched. Verified against CF 2025 in tests/cfm/verifyclient_test.cfm. |
| Week | int | date: any |
✅ Yes | |
| Wrap | string | string: string, limit: numeric, strip: boolean = false |
✅ Yes | Wraps text so lines do not exceed limit |
| WriteDump | string | variable: any [, output: string] [, format: string = "html"] [, abort: boolean] [, label: string] [, metainfo: boolean] [, top: numeric] [, show: string] [, hide: string] [, keys: numeric] [, expand: boolean] [, showUDFs: boolean] |
✅ Yes | Byte-matched against CF 2021: once-per-page STYLE block before the first HTML dump, HTML table templates, HTML 4.0 named-entity escaping, %.12g float formatting with 3-digit exponent, label propagation to nested containers, keys/show/hide/top filters, format="text" (output), abort throws page abort after emitting, float literal text preserved ( |
| WriteLog | string | text: string [, type: string = "Information"] [, application: string] [, file: string] [, log: string] |
✅ Yes | Appends CSV rows to /var/log/webstrada/.log (default application.log; env override WEBSTRADA_LOG_DIR); header row written only for new files; severity Information/Warning/Error/Fatal (unknown types fall back to Information); all fields quoted, embedded quotes doubled; date MM/DD/YY and time HH:MM:SS local; returns empty string |
| WriteOutput | ✅ Yes | |||
| WSGetAllChannels | ❌ No | |||
| WSGetSubscribers | ❌ No | |||
| WSPublish | ❌ No | |||
| WSSendMessage | ❌ No | |||
| XmlChildPos | numeric | xmlNode: xml, childName: string, position: numeric |
✅ Yes | Returns 1-based position of nth child |
| XmlElemNew | xml | xmlDoc: xml, name: string |
✅ Yes | Creates new element |
| XmlFormat | string | string: string |
✅ Yes | Escapes XML special chars |
| XmlGetNodeType | string | xmlNode: xml |
✅ Yes | Returns CF DOM node type names (ELEMENT_NODE, DOCUMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE, ...) |
| XmlNew | xml | [caseSensitive: boolean] |
✅ Yes | Creates new empty XML document |
| XmlParse | xml | xmlString: string [, caseSensitive: boolean [, validator: string]] |
✅ Yes | Parses XML string into document object; a parse failure throws CF's An error occurred while Parsing an XML document. Expression error (same message as <cfxml>; previously it reported its own "XmlParse: Failed to parse XML markup" wording) — verified against CF 2025 in tests/cfm/xmlparse_error_test.cfm + CfXmlTagTest.XmlParseErrorMessageMatchesCf |
| XmlSearch | array | xmlNode: xml, xpath: string |
✅ Yes | XPath search, returns array of nodes |
| XmlTransform | string | xml: any, xsl: string |
✅ Yes | XSLT transform |
| XmlValidate | struct | xml: any [, schema: string] |
✅ Yes | Validates XML, returns struct with status/errors; without a schema status is NO (not validated), matching CF 2021 |
| Year | int | date: any |
✅ Yes | |
| YesNoFormat | string | value: any |
✅ Yes |