Both matched their function name but had empty branch bodies, silently
producing nothing regardless of arguments. reverse(str) follows the same
single-argument shape as trim/toupper/tolower; concat(a, b, ...) follows
the same variadic comma-separated shape as max/min.
reverse() converts to std::string and reverses in place via the already-
imported <algorithm> (std::reverse), then assigns back through MString's
std::string constructor. concat() starts from the first argument and
appends each subsequent one via MString's own operator<<(ZString).
Verified via real jet-2.0 runs: reverse('hello') -> "olleh",
concat('a','b','c') -> "abc", concat('x','y') -> "xy". Full 9-script
regression suite (custom tag scoping, dump tag tree, try/catch, if/else,
keyword sigil) still passes unchanged.
Found while writing a new Expressions Reference chapter for the manual,
where every function got run for real before being documented rather
than trusted from reading the source alone - substring('helloworld', 7)
threw "operand is not valid." immediately after producing the correct
answer, confirmed via debug tracing showing an extra Operand constructed
on empty input right after the 2-arg case finished.
Root cause: a missing pair of braces around the 3-argument-only branch
meant Operand parm3(in, tag) and its own closing-paren check ran
unconditionally, even after the 2-arg form's own "if(in.ifNext(')'))"
check had already matched and produced string = parm1.string.substring(
parm2.string.asInteger()) - so it immediately tried to parse a third
argument from whatever was left (nothing, since the real closing paren
was already consumed), and construction on empty input always throws.
The 3-argument form (substring(str, start, length)) was unaffected,
since its own path never hit the first if-branch's early exit.
Fixed by properly scoping the 3-arg branch inside an else, so it only
runs when the 2-arg form's own closing paren wasn't found. Verified both
forms with real jet-2.0 runs: substring('helloworld', 7) -> "rld",
substring('helloworld', 0, 5) -> "hello".
When resolving a bare $[var]/#[var] reference, Operand set .boolean =
true whenever the value's text was EITHER "true" OR "false" -
effectively "boolean = looks-boolean-ish" rather than reflecting which
of the two it actually was. <if expr="..">/<while expr="..">check
exactly this .boolean field (see __if.cpp/__while.cpp), so <if
expr="$[flag]"> took the true branch even when flag held the literal
text "false".
Fixed to boolean = (string == "true").
Verified live: a variable set to value="false" now correctly takes an
<if expr="$[var]">'s false branch, and value="true" still correctly
takes the true branch. Re-ran tests/posttest.sh - unaffected, no
regression.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>