Fix @listen %0 to retain fancy quotes (ISSUES #1)

strip_fancy_quotes was applied to msgPlain before both pattern
matching and wildcard capture, so %0/%1/etc. received ASCII
quotes instead of the original typographic ones.

Now: match against a normalized copy (ASCII quotes) to determine
if the pattern hits, then re-capture from the original text so
%0 retains Unicode smart quotes for display. Applied to both
the @listen path in notify_check and the ^-listen path in
atr_match1.

Updated TC001 expected hash to reflect fancy quotes in %0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stephen Dennis 2026-03-18 21:04:56 -06:00
parent aeaeffb0ad
commit aa40a46c35
4 changed files with 255 additions and 120 deletions

View file

@ -2275,9 +2275,10 @@ Related Topics: @name.
Example: @listen camera = * has arrived.
@ahear camera = @va me = %va %0
Note: Unicode smart quotes (\u201C \u201D \u2018 \u2019) in speech are normalized to
ASCII " and ' before pattern matching, so patterns using plain ASCII quotes
will match speech containing typographic quotes.
Note: Unicode smart quotes (\u201C \u201D \u2018 \u2019) in speech are treated as
equivalent to ASCII " and ' for pattern matching, so patterns using plain
quotes will match speech containing typographic quotes. The captured text
in %0 etc. retains the original typographic quotes.
Related Topics: @aahear, @ahear, @amhear, @sweep, @inprefix, @infilter.

View file

@ -306,19 +306,50 @@ static int atr_match1
}
*s++ = '\0';
unescape_pattern_colons(buff + 1);
// For ^-listens, normalize the pattern so ASCII quotes match
// fancy quotes. Match against a normalized copy of the message
// text, but re-capture from the original so %0 retains fancy
// quotes for display.
//
UTF8 *match_str = (aflags & AF_NOPARSE) ? raw_str : str;
UTF8 *strNorm = nullptr;
if (AMATCH_LISTEN == type)
{
strip_fancy_quotes(buff + 1);
strNorm = alloc_lbuf("atr_match1.norm");
mux_strncpy(strNorm, match_str, LBUF_SIZE - 1);
strip_fancy_quotes(strNorm);
}
UTF8 *match_subject = strNorm ? strNorm : match_str;
UTF8 *args[NUM_ENV_VARS];
if ( ( 0 != (aflags & AF_REGEXP)
&& regexp_match(buff + 1, (aflags & AF_NOPARSE) ? raw_str : str,
&& regexp_match(buff + 1, match_subject,
((aflags & AF_CASE) ? PCRE2_CASELESS : 0), args, NUM_ENV_VARS))
|| ( 0 == (aflags & AF_REGEXP)
&& wild(buff + 1, (aflags & AF_NOPARSE) ? raw_str : str,
&& wild(buff + 1, match_subject,
args, NUM_ENV_VARS)))
{
// If we matched on normalized text, re-capture from the
// original so %0 etc. contain fancy quotes.
//
if (strNorm)
{
for (int j = 0; j < NUM_ENV_VARS; j++)
{
if (args[j]) { free_lbuf(args[j]); args[j] = nullptr; }
}
if (0 != (aflags & AF_REGEXP))
{
regexp_match(buff + 1, match_str,
((aflags & AF_CASE) ? PCRE2_CASELESS : 0), args, NUM_ENV_VARS);
}
else
{
wild(buff + 1, match_str, args, NUM_ENV_VARS);
}
}
match = 1;
CLinearTimeAbsolute lta;
wait_que(thing, player, player, AttrTrace(aflags, 0), false, lta,
@ -335,6 +366,10 @@ static int atr_match1
}
}
}
if (strNorm)
{
free_lbuf(strNorm);
}
}
atr_pop();
@ -845,7 +880,14 @@ void notify_check(dbref target, dbref sender, const UTF8 *msg, int key)
co_strip_color(reinterpret_cast<unsigned char *>(msgPlain),
reinterpret_cast<const unsigned char *>(msg),
mux_strlen(msg));
strip_fancy_quotes(msgPlain);
// Normalized copy for pattern matching — ASCII quotes only.
// msgPlain retains fancy quotes for %0 capture and display.
//
UTF8 *msgNorm = alloc_lbuf("notify_check.norm");
mux_strncpy(msgNorm, msgPlain, LBUF_SIZE - 1);
strip_fancy_quotes(msgNorm);
bool pass_listen = false;
UTF8 *args[NUM_ENV_VARS];
nargs = 0;
@ -855,8 +897,15 @@ void notify_check(dbref target, dbref sender, const UTF8 *msg, int key)
{
tp = atr_get("notify_check.790", target, A_LISTEN, &aowner, &aflags);
strip_fancy_quotes(tp);
if (*tp && wild(tp, msgPlain, args, NUM_ENV_VARS))
if (*tp && wild(tp, msgNorm, args, NUM_ENV_VARS))
{
// Re-capture from original text so %0 has fancy quotes.
//
for (int j = 0; j < NUM_ENV_VARS; j++)
{
if (args[j]) { free_lbuf(args[j]); args[j] = nullptr; }
}
wild(tp, msgPlain, args, NUM_ENV_VARS);
for (nargs = NUM_ENV_VARS; nargs && (!args[nargs - 1] || !(*args[nargs - 1])); nargs--)
{
; // Nothing
@ -1121,6 +1170,7 @@ void notify_check(dbref target, dbref sender, const UTF8 *msg, int key)
notify_check(targetloc, sender, msgFinal,
MSG_ME | MSG_F_UP | MSG_S_INSIDE | (key & (MSG_SRC_MASK | MSG_SAYPOSE | MSG_OOC)));
}
free_lbuf(msgNorm);
free_lbuf(msgPlain);
}
free_lbuf(msgFinal);

View file

@ -29,7 +29,7 @@
t(chr(256)),
strmatch(
setr(0,v(va)),
F5D08777EA6D0F93BDADAE7D2504087DFBA85F7D
01C65FD8FBBD1784A0EC8B850A16D059DA873EDB
)
),
cand(

View file

@ -1,6 +1,6 @@
+X996101
+S205
+N363
+N377
-R1
+A256
"1:TR.TC000"
@ -147,74 +147,102 @@
+A327
"1:TR.TC032"
+A328
"1:TR.TC033"
"1:LUA_FDIV"
+A329
"1:TR.TC034"
"1:TR.TC033"
+A330
"1:TR.TC035"
"1:LUA_FCONST"
+A331
"1:TR.TC036"
"1:TR.TC034"
+A332
"1:TR.TC037"
"1:LUA_MIXED"
+A333
"1:TR.TC038"
"1:TR.TC035"
+A334
"1:TR.TC039"
"1:LUA_FNEG"
+A335
"1:TR.TC040"
"1:TR.TC036"
+A336
"1:TR.TC041"
"1:LUA_CONCAT"
+A337
"1:TR.TC042"
"1:TR.TC037"
+A338
"1:TR.TC043"
"1:LUA_CONCATI"
+A339
"1:TR.TC044"
"1:TR.TC038"
+A340
"1:TR.TC045"
"1:LUA_SLEN"
+A341
"1:TR.TC046"
"1:TR.TC039"
+A342
"1:TR.TC047"
"1:LUA_NOT"
+A343
"1:TR.TC048"
"1:TR.TC040"
+A344
"1:TR.TC049"
"1:LUA_TBLSUM"
+A345
"1:TR.TC050"
"1:TR.TC041"
+A346
"1:TR.TC051"
"1:LUA_TBLSTR"
+A347
"1:TR.TC052"
"1:TR.TC042"
+A348
"1:ALPHA"
"1:LUA_MATHFLOOR"
+A349
"1:ALPHA_ONE"
"1:TR.TC043"
+A350
"1:ALPHA_TWO"
"1:LUA_STRUPPER"
+A351
"1:BETA"
"1:TR.TC044"
+A352
"1:BETA_ONE"
"1:LUA_TOSTR"
+A353
"1:GAMMA"
"1:TR.TC045"
+A354
"1:TR.TC053"
"1:LUA_MATHSQRT"
+A355
"1:TR.TC054"
"1:TR.TC046"
+A356
"1:TR.TC055"
"1:TR.TC047"
+A357
"1:SUITE.LIST.1"
"1:TR.TC048"
+A358
"1:SUITE.LIST.2"
"1:TR.TC049"
+A359
"1:SUITE.TR"
"1:TR.TC050"
+A360
"1:FN_STRLEN"
"1:TR.TC051"
+A361
"1:FN_NEG"
"1:TR.TC052"
+A362
"1:ALPHA"
+A363
"1:ALPHA_ONE"
+A364
"1:ALPHA_TWO"
+A365
"1:BETA"
+A366
"1:BETA_ONE"
+A367
"1:GAMMA"
+A368
"1:TR.TC053"
+A369
"1:TR.TC054"
+A370
"1:TR.TC055"
+A371
"1:SUITE.LIST.1"
+A372
"1:SUITE.LIST.2"
+A373
"1:SUITE.TR"
+A374
"1:FN_STRLEN"
+A375
"1:FN_NEG"
+A376
"1:FN_IDENT"
!0
"Limbo"
@ -260,7 +288,7 @@
>84
"#1;127.0.0.1;Fri Jan 01 00:00:00 2010;;;;;;;0;0;;;;;;;"
>88
"127.0.0.1"
"localhost"
>144
"127.0.0.1"
>213
@ -1207,7 +1235,7 @@
>256
"@log smoke=Beginning say test cases."
>257
"@listen test_cmd_say=*;say Where is the movie theater?;say add(1,1) add(1,1) [add(1,1)];say/noeval add(1,1) add(1,1) [add(1,1)];nsay add(1,1) add(1,1) [add(1,1)];@wait 0={@if cor(cand(t(chr(256)),strmatch(setr(0,v(va)),F5D08777EA6D0F93BDADAE7D2504087DFBA85F7D)),cand(not(chr(256)),strmatch(setr(0,v(va)),F5D08777EA6D0F93BDADAE7D2504087DFBA85F7D)))={@log smoke=TC001: say examples. Succeeded.},{@log smoke=TC001: say examples. Failed (%q0).};@listen test_cmd_say=;@trig me/tr.done}"
"@listen test_cmd_say=*;say Where is the movie theater?;say add(1,1) add(1,1) [add(1,1)];say/noeval add(1,1) add(1,1) [add(1,1)];nsay add(1,1) add(1,1) [add(1,1)];@wait 0={@if cor(cand(t(chr(256)),strmatch(setr(0,v(va)),01C65FD8FBBD1784A0EC8B850A16D059DA873EDB)),cand(not(chr(256)),strmatch(setr(0,v(va)),F5D08777EA6D0F93BDADAE7D2504087DFBA85F7D)))={@log smoke=TC001: say examples. Succeeded.},{@log smoke=TC001: say examples. Failed (%q0).};@listen test_cmd_say=;@trig me/tr.done}"
>258
"@log smoke=End say test cases.;@notify smoke"
<
@ -3754,7 +3782,63 @@
>326
"local s=0 for i=1,10000 do s=s+i end return s"
>327
"@if strmatch(setr(0,lua(me/LUA_BIGLOOP)),50005000)={@log smoke=TC032: lua large for-loop. Succeeded.;@trig me/tr.done},{@log smoke=TC032: lua large for-loop. Failed (%q0).;@trig me/tr.done}"
"@if strmatch(setr(0,lua(me/LUA_BIGLOOP)),50005000)={@log smoke=TC032: lua large for-loop. Succeeded.},{@log smoke=TC032: lua large for-loop. Failed (%q0).}"
>328
"return 10 / 3"
>329
"@if strmatch(setr(0,lua(me/LUA_FDIV)),3.333333*)={@log smoke=TC033: lua float division. Succeeded.},{@log smoke=TC033: lua float division. Failed (%q0).}"
>330
"return 1.5 + 2.5"
>331
"@if strmatch(setr(0,lua(me/LUA_FCONST)),4*)={@log smoke=TC034: lua float constant add. Succeeded.},{@log smoke=TC034: lua float constant add. Failed (%q0).}"
>332
"return 3 + 1.5"
>333
"@if strmatch(setr(0,lua(me/LUA_MIXED)),4.5)={@log smoke=TC035: lua mixed int+float. Succeeded.},{@log smoke=TC035: lua mixed int+float. Failed (%q0).}"
>334
"return -(3.14)"
>335
"@if strmatch(setr(0,lua(me/LUA_FNEG)),-3.14)={@log smoke=TC036: lua float negation. Succeeded.},{@log smoke=TC036: lua float negation. Failed (%q0).}"
>336
"return \"hello\" .. \" \" .. \"world\""
>337
"@if strmatch(setr(0,lua(me/LUA_CONCAT)),hello world)={@log smoke=TC037: lua string concat op. Succeeded.},{@log smoke=TC037: lua string concat op. Failed (%q0).}"
>338
"return \"count: \" .. 42"
>339
"@if strmatch(setr(0,lua(me/LUA_CONCATI)),count: 42)={@log smoke=TC038: lua concat int coercion. Succeeded.},{@log smoke=TC038: lua concat int coercion. Failed (%q0).}"
>340
"return #\"hello\""
>341
"@if strmatch(setr(0,lua(me/LUA_SLEN)),5)={@log smoke=TC039: lua string length. Succeeded.},{@log smoke=TC039: lua string length. Failed (%q0).}"
>342
"if not false then return \"yes\" else return \"no\" end"
>343
"@if strmatch(setr(0,lua(me/LUA_NOT)),yes)={@log smoke=TC040: lua logical not. Succeeded.},{@log smoke=TC040: lua logical not. Failed (%q0).}"
>344
"local t={10,20,30} return t[1]+t[2]+t[3]"
>345
"@if strmatch(setr(0,lua(me/LUA_TBLSUM)),60)={@log smoke=TC041: lua table construct+access. Succeeded.},{@log smoke=TC041: lua table construct+access. Failed (%q0).}"
>346
"local t={\"hello\",\"world\"} return t[1] .. \" \" .. t[2]"
>347
"@if strmatch(setr(0,lua(me/LUA_TBLSTR)),hello world)={@log smoke=TC042: lua table string concat. Succeeded.},{@log smoke=TC042: lua table string concat. Failed (%q0).}"
>348
"return math.floor(3.7)"
>349
"@if strmatch(setr(0,lua(me/LUA_MATHFLOOR)),3)={@log smoke=TC043: lua math.floor. Succeeded.},{@log smoke=TC043: lua math.floor. Failed (%q0).}"
>350
"return string.upper(\"hello\")"
>351
"@if strmatch(setr(0,lua(me/LUA_STRUPPER)),HELLO)={@log smoke=TC044: lua string.upper. Succeeded.},{@log smoke=TC044: lua string.upper. Failed (%q0).}"
>352
"return tostring(42)"
>353
"@if strmatch(setr(0,lua(me/LUA_TOSTR)),42)={@log smoke=TC045: lua tostring. Succeeded.},{@log smoke=TC045: lua tostring. Failed (%q0).}"
>354
"return math.sqrt(144)"
>355
"@if strmatch(setr(0,lua(me/LUA_MATHSQRT)),12*)={@log smoke=TC046: lua math.sqrt. Succeeded.;@trig me/tr.done},{@log smoke=TC046: lua math.sqrt. Failed (%q0).;@trig me/tr.done}"
<
!114
"test_mailsend_fn"
@ -4411,45 +4495,45 @@
"@if strmatch(setr(0,sha1([setr(3,MIX)][eval({%q<bad notafunc(%q3 [add(2,2)]})])),9D9AFFAEDBF792FD38E2F2C2383FA31C9C387AFA)={@log smoke=TC031: edge mixed malformed sequence parity. Succeeded.},{@log smoke=TC031: edge mixed malformed sequence parity. Failed (%q0).}"
>327
"@if strmatch(setr(0,sha1([eval({{foo(bar},baz)})])),setr(1,sha1({foo(bar},baz))))={@log smoke=TC032: edge deep brace/paren mismatch parity. Succeeded (possibly fixed).;},{@log smoke=TC032: edge deep brace/paren mismatch divergence observed (%q0 vs %q1).;}"
>328
"@if strmatch(setr(0,sha1([eval({A%q<bad;next})])),setr(1,sha1([eval({A%q<bad;next})])))={@log smoke=TC033: edge malformed pct-q + semicolon parity. Succeeded.},{@log smoke=TC033: edge malformed pct-q + semicolon parity. Failed (%q0 vs %q1).}"
>329
"@if strmatch(setr(0,sha1([eval({A%x<bad;next})])),setr(1,sha1([eval({A%x<bad;next})])))={@log smoke=TC034: edge malformed pct-x + semicolon parity. Succeeded.},{@log smoke=TC034: edge malformed pct-x + semicolon parity. Failed (%q0 vs %q1).}"
>330
"@if strmatch(setr(0,sha1([eval({A%=<bad;next})])),setr(1,sha1([eval({A%=<bad;next})])))={@log smoke=TC035: edge malformed pct-equals + semicolon parity. Succeeded.},{@log smoke=TC035: edge malformed pct-equals + semicolon parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%q<bad;next})])),setr(1,sha1([eval({A%q<bad;next})])))={@log smoke=TC033: edge malformed pct-q + semicolon parity. Succeeded.},{@log smoke=TC033: edge malformed pct-q + semicolon parity. Failed (%q0 vs %q1).}"
>331
"@if strmatch(setr(0,sha1([eval({A%q<bad(foo})])),setr(1,sha1([eval({A%q<bad(foo})])))={@log smoke=TC036: edge malformed pct-q + open paren parity. Succeeded.},{@log smoke=TC036: edge malformed pct-q + open paren parity. Failed (%q0 vs %q1).}"
>332
"@if strmatch(setr(0,sha1([eval({A%q<bad\\escape})])),setr(1,sha1([eval({A%q<bad\\escape})])))={@log smoke=TC037: edge malformed pct-q + backslash parity. Succeeded.},{@log smoke=TC037: edge malformed pct-q + backslash parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%x<bad;next})])),setr(1,sha1([eval({A%x<bad;next})])))={@log smoke=TC034: edge malformed pct-x + semicolon parity. Succeeded.},{@log smoke=TC034: edge malformed pct-x + semicolon parity. Failed (%q0 vs %q1).}"
>333
"@if strmatch(setr(0,sha1([setr(4,NEST)][eval({A%q<bad%q4})])),setr(1,sha1([setr(4,NEST)][eval({A%q<bad%q4})])))={@log smoke=TC038: edge malformed pct-q + nested percent parity. Succeeded.},{@log smoke=TC038: edge malformed pct-q + nested percent parity. Failed (%q0 vs %q1).}"
>334
"@if strmatch(setr(0,sha1([eval({A%q<bad]tail})][eval({A%q<bad}tail})])),setr(1,sha1([eval({A%q<bad]tail})][eval({A%q<bad}tail})])))={@log smoke=TC039: edge malformed pct-q + close delimiters parity. Succeeded.},{@log smoke=TC039: edge malformed pct-q + close delimiters parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%=<bad;next})])),setr(1,sha1([eval({A%=<bad;next})])))={@log smoke=TC035: edge malformed pct-equals + semicolon parity. Succeeded.},{@log smoke=TC035: edge malformed pct-equals + semicolon parity. Failed (%q0 vs %q1).}"
>335
"@if strmatch(setr(0,sha1([eval({A%q<bad;B[add(1,2)]C})])),setr(1,sha1([eval({A%q<bad;B[add(1,2)]C})])))={@log smoke=TC040: edge malformed angle + bracket expression parity. Succeeded.},{@log smoke=TC040: edge malformed angle + bracket expression parity. Failed (%q0 vs %q1).}"
>336
"@if strmatch(setr(0,sha1([eval({A%=<bad;name>})][eval({A%=<bad(name>})])),setr(1,sha1([eval({A%=<bad;name>})][eval({A%=<bad(name>})])))={@log smoke=TC041: edge malformed pct-equals semicolon/paren parity. Succeeded.},{@log smoke=TC041: edge malformed pct-equals semicolon/paren parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%q<bad(foo})])),setr(1,sha1([eval({A%q<bad(foo})])))={@log smoke=TC036: edge malformed pct-q + open paren parity. Succeeded.},{@log smoke=TC036: edge malformed pct-q + open paren parity. Failed (%q0 vs %q1).}"
>337
"@if strmatch(setr(0,sha1([eval({%q<bad;[add(1,2)]})][eval({%x<bad;[mul(2,3)]})])),setr(1,sha1([eval({%q<bad;[add(1,2)]})][eval({%x<bad;[mul(2,3)]})])))={@log smoke=TC042: edge malformed angle + valid function tail parity. Succeeded.;},{@log smoke=TC042: edge malformed angle + valid function tail parity. Failed (%q0 vs %q1).;}"
>338
"@if strmatch(setr(0,sha1([eval({A%q<bad;[add(1,2)]})])),setr(1,sha1([eval({A%q<bad;[add(1,2)]})])))={@log smoke=TC043: edge pct-q semicolon + bracket tail parity. Succeeded.},{@log smoke=TC043: edge pct-q semicolon + bracket tail parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%q<bad\\escape})])),setr(1,sha1([eval({A%q<bad\\escape})])))={@log smoke=TC037: edge malformed pct-q + backslash parity. Succeeded.},{@log smoke=TC037: edge malformed pct-q + backslash parity. Failed (%q0 vs %q1).}"
>339
"@if strmatch(setr(0,sha1([eval({A%q<bad(foo[add(1,2)]})])),setr(1,sha1([eval({A%q<bad(foo[add(1,2)]})])))={@log smoke=TC044: edge pct-q open paren + function tail parity. Succeeded.},{@log smoke=TC044: edge pct-q open paren + function tail parity. Failed (%q0 vs %q1).}"
>340
"@if strmatch(setr(0,sha1([eval({A%x<bad;[mul(2,3)]})])),setr(1,sha1([eval({A%x<bad;[mul(2,3)]})])))={@log smoke=TC045: edge pct-x semicolon + function tail parity. Succeeded.},{@log smoke=TC045: edge pct-x semicolon + function tail parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([setr(4,NEST)][eval({A%q<bad%q4})])),setr(1,sha1([setr(4,NEST)][eval({A%q<bad%q4})])))={@log smoke=TC038: edge malformed pct-q + nested percent parity. Succeeded.},{@log smoke=TC038: edge malformed pct-q + nested percent parity. Failed (%q0 vs %q1).}"
>341
"@if strmatch(setr(0,sha1([eval({A%=<bad;[strcat(x,y)]})])),setr(1,sha1([eval({A%=<bad;[strcat(x,y)]})])))={@log smoke=TC046: edge pct-equals semicolon + function tail parity. Succeeded.},{@log smoke=TC046: edge pct-equals semicolon + function tail parity. Failed (%q0 vs %q1).}"
>342
"@if strmatch(setr(0,sha1([setr(0,QQ)][eval({A%q<bad(%q0)tail})])),setr(1,sha1([setr(0,QQ)][eval({A%q<bad(%q0)tail})])))={@log smoke=TC047: edge pct-q nested percent + parens parity. Succeeded.},{@log smoke=TC047: edge pct-q nested percent + parens parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%q<bad]tail})][eval({A%q<bad}tail})])),setr(1,sha1([eval({A%q<bad]tail})][eval({A%q<bad}tail})])))={@log smoke=TC039: edge malformed pct-q + close delimiters parity. Succeeded.},{@log smoke=TC039: edge malformed pct-q + close delimiters parity. Failed (%q0 vs %q1).}"
>343
"@if strmatch(setr(0,sha1([eval({A%q<bad;next;more})])),setr(1,sha1([eval({A%q<bad;next;more})])))={@log smoke=TC048: edge pct-q multiple semicolons parity. Succeeded.},{@log smoke=TC048: edge pct-q multiple semicolons parity. Failed (%q0 vs %q1).}"
>344
"@if strmatch(setr(0,sha1([eval({A%x<bad(]tail})])),setr(1,sha1([eval({A%x<bad(]tail})])))={@log smoke=TC049: edge pct-x mixed closer tail parity. Succeeded.},{@log smoke=TC049: edge pct-x mixed closer tail parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%q<bad;B[add(1,2)]C})])),setr(1,sha1([eval({A%q<bad;B[add(1,2)]C})])))={@log smoke=TC040: edge malformed angle + bracket expression parity. Succeeded.},{@log smoke=TC040: edge malformed angle + bracket expression parity. Failed (%q0 vs %q1).}"
>345
"@if strmatch(setr(0,sha1([eval({A%=<bad(name>;tail})])),setr(1,sha1([eval({A%=<bad(name>;tail})])))={@log smoke=TC050: edge pct-equals malformed name + semicolon parity. Succeeded.},{@log smoke=TC050: edge pct-equals malformed name + semicolon parity. Failed (%q0 vs %q1).}"
>346
"@if strmatch(setr(0,sha1([setr(1,TAIL)][eval({%q<bad;[add(1,2)]%q1})])),setr(1,sha1([setr(1,TAIL)][eval({%q<bad;[add(1,2)]%q1})])))={@log smoke=TC051: edge malformed pct-q then valid tail parity. Succeeded.},{@log smoke=TC051: edge malformed pct-q then valid tail parity. Failed (%q0 vs %q1).}"
"@if strmatch(setr(0,sha1([eval({A%=<bad;name>})][eval({A%=<bad(name>})])),setr(1,sha1([eval({A%=<bad;name>})][eval({A%=<bad(name>})])))={@log smoke=TC041: edge malformed pct-equals semicolon/paren parity. Succeeded.},{@log smoke=TC041: edge malformed pct-equals semicolon/paren parity. Failed (%q0 vs %q1).}"
>347
"@if strmatch(setr(0,sha1([eval({%q<bad;[add(1,2)]})][eval({%x<bad;[mul(2,3)]})])),setr(1,sha1([eval({%q<bad;[add(1,2)]})][eval({%x<bad;[mul(2,3)]})])))={@log smoke=TC042: edge malformed angle + valid function tail parity. Succeeded.;},{@log smoke=TC042: edge malformed angle + valid function tail parity. Failed (%q0 vs %q1).;}"
>349
"@if strmatch(setr(0,sha1([eval({A%q<bad;[add(1,2)]})])),setr(1,sha1([eval({A%q<bad;[add(1,2)]})])))={@log smoke=TC043: edge pct-q semicolon + bracket tail parity. Succeeded.},{@log smoke=TC043: edge pct-q semicolon + bracket tail parity. Failed (%q0 vs %q1).}"
>351
"@if strmatch(setr(0,sha1([eval({A%q<bad(foo[add(1,2)]})])),setr(1,sha1([eval({A%q<bad(foo[add(1,2)]})])))={@log smoke=TC044: edge pct-q open paren + function tail parity. Succeeded.},{@log smoke=TC044: edge pct-q open paren + function tail parity. Failed (%q0 vs %q1).}"
>353
"@if strmatch(setr(0,sha1([eval({A%x<bad;[mul(2,3)]})])),setr(1,sha1([eval({A%x<bad;[mul(2,3)]})])))={@log smoke=TC045: edge pct-x semicolon + function tail parity. Succeeded.},{@log smoke=TC045: edge pct-x semicolon + function tail parity. Failed (%q0 vs %q1).}"
>355
"@if strmatch(setr(0,sha1([eval({A%=<bad;[strcat(x,y)]})])),setr(1,sha1([eval({A%=<bad;[strcat(x,y)]})])))={@log smoke=TC046: edge pct-equals semicolon + function tail parity. Succeeded.},{@log smoke=TC046: edge pct-equals semicolon + function tail parity. Failed (%q0 vs %q1).}"
>356
"@if strmatch(setr(0,sha1([setr(0,QQ)][eval({A%q<bad(%q0)tail})])),setr(1,sha1([setr(0,QQ)][eval({A%q<bad(%q0)tail})])))={@log smoke=TC047: edge pct-q nested percent + parens parity. Succeeded.},{@log smoke=TC047: edge pct-q nested percent + parens parity. Failed (%q0 vs %q1).}"
>357
"@if strmatch(setr(0,sha1([eval({A%q<bad;next;more})])),setr(1,sha1([eval({A%q<bad;next;more})])))={@log smoke=TC048: edge pct-q multiple semicolons parity. Succeeded.},{@log smoke=TC048: edge pct-q multiple semicolons parity. Failed (%q0 vs %q1).}"
>358
"@if strmatch(setr(0,sha1([eval({A%x<bad(]tail})])),setr(1,sha1([eval({A%x<bad(]tail})])))={@log smoke=TC049: edge pct-x mixed closer tail parity. Succeeded.},{@log smoke=TC049: edge pct-x mixed closer tail parity. Failed (%q0 vs %q1).}"
>359
"@if strmatch(setr(0,sha1([eval({A%=<bad(name>;tail})])),setr(1,sha1([eval({A%=<bad(name>;tail})])))={@log smoke=TC050: edge pct-equals malformed name + semicolon parity. Succeeded.},{@log smoke=TC050: edge pct-equals malformed name + semicolon parity. Failed (%q0 vs %q1).}"
>360
"@if strmatch(setr(0,sha1([setr(1,TAIL)][eval({%q<bad;[add(1,2)]%q1})])),setr(1,sha1([setr(1,TAIL)][eval({%q<bad;[add(1,2)]%q1})])))={@log smoke=TC051: edge malformed pct-q then valid tail parity. Succeeded.},{@log smoke=TC051: edge malformed pct-q then valid tail parity. Failed (%q0 vs %q1).}"
>361
"@if strmatch(setr(0,sha1([setr(2,POST)][eval({pre%q<bad;mid%q2post})])),setr(1,sha1([setr(2,POST)][eval({pre%q<bad;mid%q2post})])))={@log smoke=TC052: edge malformed pct-q then later substitution parity. Succeeded.;@trig me/tr.done},{@log smoke=TC052: edge malformed pct-q then later substitution parity. Failed (%q0 vs %q1).;@trig me/tr.done}"
<
!134
@ -4678,17 +4762,17 @@
"@if strmatch(setr(0,reglattr(me,\\[invalid)),#-1 REGEXP ERROR*)={@log smoke=TC007: reglattr bad regex. Succeeded.},{@log smoke=TC007: reglattr bad regex. Failed (%q0).}"
>265
"@if strmatch(setr(0,sha1(sort(reglattr(me,_ONE$)))),0CA923E1C9FE9DE4138C1274783DCFD127997667)={@log smoke=TC008: reglattr _ONE suffix. Succeeded.;@trig me/tr.done},{@log smoke=TC008: reglattr _ONE suffix. Failed (%q0).;@trig me/tr.done}"
>348
>362
"1"
>349
>363
"2"
>350
>364
"3"
>351
>365
"4"
>352
>366
"5"
>353
>367
"6"
<
!141
@ -5143,51 +5227,51 @@
"@log smoke=BENCH031:[rvbench(strlen(cat(rand(100),rand(100))),10000)]"
>327
"@log smoke=BENCH032:[rvbench(strcat(rand(100),rand(100)),10000)]"
>328
"@log smoke=BENCH033:[rvbench(strlen(strcat(rand(100),rand(100),rand(100))),10000)]"
>329
"@log smoke=BENCH034:[rvbench(cat(rand(10),rand(10),rand(10),rand(10)),10000)]"
>330
"@log smoke=BENCH035:[rvbench(iter(a b c d e, X),10000)]"
"@log smoke=BENCH033:[rvbench(strlen(strcat(rand(100),rand(100),rand(100))),10000)]"
>331
"@log smoke=BENCH036:[rvbench(iter(1 2 3 4 5, add(##\\,1)),10000)]"
>332
"@log smoke=BENCH037:[rvbench(iter(a b c d e f g h i j, X),10000)]"
"@log smoke=BENCH034:[rvbench(cat(rand(10),rand(10),rand(10),rand(10)),10000)]"
>333
"@log smoke=BENCH038:[rvbench(iter(a b c, X),10000)]"
>334
"@log smoke=BENCH039:[rvbench(iter(a b c d e f g h i j k l m n o, X),10000)]"
"@log smoke=BENCH035:[rvbench(iter(a b c d e, X),10000)]"
>335
"@log smoke=BENCH040:[rvbench(iter(a b c d e f g h i j k l m n o p q r s t, X),10000)]"
>336
"@log smoke=BENCH041:[rvbench(iter(aa bb cc dd ee ff gg hh ii jj, X),10000)]"
"@log smoke=BENCH036:[rvbench(iter(1 2 3 4 5, add(##\\,1)),10000)]"
>337
"@log smoke=BENCH042:[rvbench(iter(a b c d e f g, X),10000)]"
>338
"@log smoke=BENCH043:[rvbench(first(cat(rand(10),rand(10),rand(10))),10000)]"
"@log smoke=BENCH037:[rvbench(iter(a b c d e f g h i j, X),10000)]"
>339
"@log smoke=BENCH044:[rvbench(rest(cat(rand(10),rand(10),rand(10))),10000)]"
>340
"@log smoke=BENCH045:[rvbench(last(cat(rand(10),rand(10),rand(10))),10000)]"
"@log smoke=BENCH038:[rvbench(iter(a b c, X),10000)]"
>341
"@log smoke=BENCH046:[rvbench(member(alpha beta gamma delta,gamma),10000)]"
>342
"@log smoke=BENCH047:[rvbench(repeat(cat(rand(10),x),5),10000)]"
"@log smoke=BENCH039:[rvbench(iter(a b c d e f g h i j k l m n o, X),10000)]"
>343
"@log smoke=BENCH048:[rvbench(trim(cat(%b,rand(100),%b)),10000)]"
>344
"@log smoke=BENCH049:[rvbench(before(cat(rand(10),|,rand(10)),|),10000)]"
"@log smoke=BENCH040:[rvbench(iter(a b c d e f g h i j k l m n o p q r s t, X),10000)]"
>345
"@log smoke=BENCH050:[rvbench(after(cat(rand(10),|,rand(10)),|),10000)]"
>346
"@log smoke=BENCH051:[rvbench(ldelete(cat(rand(10),rand(10),rand(10),rand(10)),2),10000)]"
"@log smoke=BENCH041:[rvbench(iter(aa bb cc dd ee ff gg hh ii jj, X),10000)]"
>347
"@log smoke=BENCH052:[rvbench(replace(cat(rand(10),rand(10),rand(10)),2,ZZZ),10000)]"
>354
"@log smoke=BENCH053:[rvbench(insert(cat(rand(10),rand(10),rand(10)),2,ZZZ),10000)]"
"@log smoke=BENCH042:[rvbench(iter(a b c d e f g, X),10000)]"
>349
"@log smoke=BENCH043:[rvbench(first(cat(rand(10),rand(10),rand(10))),10000)]"
>351
"@log smoke=BENCH044:[rvbench(rest(cat(rand(10),rand(10),rand(10))),10000)]"
>353
"@log smoke=BENCH045:[rvbench(last(cat(rand(10),rand(10),rand(10))),10000)]"
>355
"@log smoke=BENCH054:[rvbench(first(rest(cat(rand(10),rand(10),rand(10),rand(10)))),10000)]"
"@log smoke=BENCH046:[rvbench(member(alpha beta gamma delta,gamma),10000)]"
>356
"@log smoke=BENCH047:[rvbench(repeat(cat(rand(10),x),5),10000)]"
>357
"@log smoke=BENCH048:[rvbench(trim(cat(%b,rand(100),%b)),10000)]"
>358
"@log smoke=BENCH049:[rvbench(before(cat(rand(10),|,rand(10)),|),10000)]"
>359
"@log smoke=BENCH050:[rvbench(after(cat(rand(10),|,rand(10)),|),10000)]"
>360
"@log smoke=BENCH051:[rvbench(ldelete(cat(rand(10),rand(10),rand(10),rand(10)),2),10000)]"
>361
"@log smoke=BENCH052:[rvbench(replace(cat(rand(10),rand(10),rand(10)),2,ZZZ),10000)]"
>368
"@log smoke=BENCH053:[rvbench(insert(cat(rand(10),rand(10),rand(10)),2,ZZZ),10000)]"
>369
"@log smoke=BENCH054:[rvbench(first(rest(cat(rand(10),rand(10),rand(10),rand(10)))),10000)]"
>370
"@log smoke=BENCH055:[rvbench(iter(a|1 b|2 c|3 d|4 e|5, before(##\\,|)),10000)];@trig me/tr.done"
<
!154
@ -5562,11 +5646,11 @@
"Fri Jan 01 00:00:00 2010"
>219
"Fri Jan 01 00:00:00 2010"
>357
>371
"abs_fn accent_fn acos_fn add_fn after_fn alphamax_fn alphamin_fn and_fn andbool_fn asin_fn atan_fn atan2_fn attrib_set_fn band_fn baseconv_fn before_fn benchmark_fn bnand_fn bor_fn bxor_fn cand_fn candbool_fn capstr_fn case_fn cat_fn ceil_fn center_fn choose_fn chr_fn citer_fn clone_fn cmd_say columns_fn comp_fn convtime_fn connlog_fn cor_fn corbool_fn cos_fn cpad_fn crc32_fn cron_fn ctu_fn dec_fn delete_fn digest_fn digittime_fn dist2d_fn dist3d_fn dynhelp_fn e_fn edit_fn elements_fn encode64_fn eq_fn escape_fn exp3_fn exp_fn exptime_fn extract_fn fdiv_fn first_fn floor_fn floordiv_fn fmod_fn grab_fn graball_fn gt_fn gte_fn hmac_fn iadd_fn iabs_fn idiv_fn imul_fn inc_fn index_fn insert_fn isalpha_fn isdbref_fn isign_fn isint_fn isjson_fn isnum_fn israt_fn isub_fn isword_fn itemize_fn json_fn letq_fn"
>358
>372
"ladd_fn land_fn last_fn lcstr_fn ldelete_fn ljust_fn lmax_fn lmin_fn ln_fn lnum_fn locate_poss lock_fn lockencode_fn log_fn lor_fn lpad_fn lpos_fn lt_fn lte_fn mailsend_fn match_fn matchall_fn max_fn member_fn merge_fn mid_fn min_fn mod_fn mul_fn neq_fn noeval_fn not_fn objid_fn oemit_fn or_fn orbool_fn ord_fn parser_fn parenmatch_fn pi_fn pickrand_fn pos_fn power_fn printf_fn protect_fn reglattr_fn remainder_fn remove_fn repeat_fn replace_fn rest_fn reverse_fn revwords_fn right_fn rjust_fn roman_fn round_fn rpad_fn rvbench_fn secure_fn setdiff_fn setinter_fn setunion_fn sha1_fn shl_fn shr_fn shuffle_fn sign_fn sin_fn singletime_fn sort_fn sortkey_fn space_fn spellnum_fn splice_fn sqrt_fn squish_fn strcat_fn strip_fn stripansi_fn strlen_fn strmatch_fn strmem_fn strdistance_fn strtrunc_fn sub_fn switch_fn t_fn table_fn tan_fn tr_fn translate_fn trim_fn trunc_fn ucstr_fn url_escape_fn vadd_fn vcross_fn vdot_fn vmag_fn vmul_fn vsub_fn vunit_fn visual_width_fn wordpos_fn words_fn wrap_fn xor_fn jit_parity_fn jitstats_fn lua_fn shutdown"
>359
>373
"@wait me={@dolist lattr(test_%0/tr.tc*)=@trig test_%0/##}"
<
!167
@ -5638,11 +5722,11 @@
"@if strmatch(setr(0,eq(strlen(sortkey(me/fn_ident,)),0)),1)={@log smoke=TC005: sortkey empty list. Succeeded.},{@log smoke=TC005: sortkey empty list. Failed (%q0).}"
>263
"@if strmatch(setr(0,sortkey(me/fn_ident,only)),only)={@log smoke=TC006: sortkey single element. Succeeded.;@trig me/tr.done},{@log smoke=TC006: sortkey single element. Failed (%q0).;@trig me/tr.done}"
>360
>374
"strlen(%0)"
>361
>375
"mul(%0,-1)"
>362
>376
"%0"
<
!169