From 7e9a2da8de7a9a351b31cc7f0e1a4af2ecf5e795 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 10 Oct 2026 10:30:30 -0400 Subject: [PATCH] Text given for a key to ll or llcompat is given as it is, as LSL's function took it SLua's ll and llcompat take a key argument as a uuid or as any string, and the function itself decides whether the text is a key, as LSL's did. The converter wrapped such text in uuid(), which the definitions' uuid parameters asked for. But uuid() stops the script on text that is no UUID: with no "selected" in the JSON, llKey2Name(llJsonGetValue(msg, ["selected"])) answered "" in LSL, and its conversion stopped the script. The text is now given as it is, `:: any` telling the type checker, with a note once a script saying why. A UUID written out is still a uuid, and the script's own function's key parameter still takes uuid(), since its body may compare it with one. allsltoslua 84 (strict too, and close to LSL), and 23, whose llGetOwnerKey("abc") is no longer uuid("abc"). Co-Authored-By: Claude Opus 5.5 --- indra/alscript/lsl/allsltoslua.cpp | 45 ++++++++++++++---- indra/alscript/tests/allsltoslua_test.cpp | 46 ++++++++++++++++++- .../newview/skins/default/xui/en/strings.xml | 1 + 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/indra/alscript/lsl/allsltoslua.cpp b/indra/alscript/lsl/allsltoslua.cpp index 061ead39e6..1a999d7f1c 100644 --- a/indra/alscript/lsl/allsltoslua.cpp +++ b/indra/alscript/lsl/allsltoslua.cpp @@ -607,9 +607,17 @@ namespace Expr sideEffect(LSLExpression* e); // The arguments, each as its parameter's type -- but those SLua takes // either text or a uuid for (`text`, a bit for each by its place), - // as they are. + // as they are; and, for ll's or llcompat's (`builtin`), text given + // for a key (Writer::textAsKey). // One may be given written already: its place, and its text. - std::string args(LSLASTNode* list, LSLParamList* params, U16 text = 0, int put = -1, const std::string& put_text = {}); + std::string args(LSLASTNode* list, LSLParamList* params, U16 text = 0, int put = -1, const std::string& put_text = {}, + bool builtin = false); + // Text given where ll or llcompat takes a key: as it is, which they + // take as LSL's functions did, where uuid() would stop the script + // on text that is no UUID, which LSL's function answered as it + // answers a key of nothing; `:: any` tells the type checker, which + // asks for a uuid. A UUID written out is a uuid, as anywhere. + std::string textAsKey(LSLExpression* given); // What SLua has in a library call's stead, where it means the same; // nothing where it has nothing. std::optional idiom(LSLFunctionExpression* e, const std::string& lsl, std::string& called); @@ -2391,7 +2399,23 @@ namespace return out; } - std::string Writer::args(LSLASTNode* list, LSLParamList* params, U16 text, int put, const std::string& put_text) + std::string Writer::textAsKey(LSLExpression* given) + { + LSLExpression* inner = unbracketed(given); + const bool uuid = inner && inner->getNodeSubType() == NODE_CONSTANT_EXPRESSION && + inner->getChild(0)->getNodeSubType() == NODE_STRING_CONSTANT && + ALLSLTraits::isUuid(static_cast(inner->getChild(0))->getValue()); + if (slType(given) != LST_STRING || uuid) + { + return coerced(given, LST_KEY).text; + } + noteOnce(given, "SluaKeyAsText", + "SLua's ll takes a key as text, as LSL's functions did, so text is given as it is; `:: any` tells the type checker, which " + "asks for a uuid. uuid() would stop the script on text that is no UUID."); + return bracketed(value(given), PRIMARY) + " :: any"; + } + + std::string Writer::args(LSLASTNode* list, LSLParamList* params, U16 text, int put, const std::string& put_text, bool builtin) { std::string out; LSLASTNode* param = params ? params->getChild(0) : nullptr; @@ -2406,7 +2430,10 @@ namespace to = slType(given); } // A parameter the script only reads as a truth is given one. - out += (out.empty() ? "" : ", ") + (at == put ? put_text : boolean(taken) ? truthOf(given) : coerced(given, to).text); + out += (out.empty() ? "" : ", ") + (at == put ? put_text + : boolean(taken) ? truthOf(given) + : builtin && to == LST_KEY ? textAsKey(given) + : coerced(given, to).text); param = param ? param->getNext() : nullptr; } return out; @@ -2663,7 +2690,7 @@ namespace else { const LSLIType to = param ? param->getIType() : static_cast(arg)->getIType(); - one = coerced(static_cast(arg), to).text; + one = to == LST_KEY ? textAsKey(static_cast(arg)) : coerced(static_cast(arg), to).text; } out += (out.empty() ? "" : ", ") + one; param = param ? param->getNext() : nullptr; @@ -3117,7 +3144,7 @@ namespace table += lines ? listIndent(0) + "}" : " }"; called = "ll." + lsl.substr(2); LSLParamList* params = e->getIdentifier()->getSymbol()->getFunctionDecl(); - return Expr{ called + "(" + args(e->getArguments(), params, 0, call->at, table) + ")" }; + return Expr{ called + "(" + args(e->getArguments(), params, 0, call->at, table, true) + ")" }; } std::optional Writer::idiom(LSLFunctionExpression* e, const std::string& lsl, std::string& called) @@ -3446,7 +3473,7 @@ namespace noteOnce(e, "SluaUse", "SLua would use [1] for [2].", { trait->sluaUse, lsl }); } const std::string args_text = - (slua & ALLSLTraits::SluaIndexArgs) ? llArgs(e, indexes) : args(e->getArguments(), params, trait ? trait->sluaTextArgs : 0); + (slua & ALLSLTraits::SluaIndexArgs) ? llArgs(e, indexes) : args(e->getArguments(), params, trait ? trait->sluaTextArgs : 0, -1, {}, true); return { called + "(" + args_text + ")", PRIMARY, (slua & ALLSLTraits::SluaBool) != 0 }; } // An index SLua's ll counts from 1, or nil for none, read as LSL's: @@ -3454,7 +3481,7 @@ namespace const U8 not_ll = compat_only & ~ALLSLTraits::SluaIndexResult; if (mOptions.sluaCalls && (slua & ALLSLTraits::SluaIndexResult) && !(slua & not_ll) && ll_indexes) { - const std::string args_text = (slua & ALLSLTraits::SluaIndexArgs) ? llArgs(e, indexes) : args(e->getArguments(), params); + const std::string args_text = (slua & ALLSLTraits::SluaIndexArgs) ? llArgs(e, indexes) : args(e->getArguments(), params, 0, -1, {}, true); called = "ll." + bare; return { "(" + called + "(" + args_text + ") or 0) - 1", ADD }; } @@ -3499,7 +3526,7 @@ namespace noteOnce(e, "SluaUse", "SLua would use [1] for [2].", { trait->sluaUse, lsl }); } called = "llcompat." + bare; - return { called + "(" + args(e->getArguments(), params) + ")" }; + return { called + "(" + args(e->getArguments(), params, 0, -1, {}, true) + ")" }; } Expr Writer::signedUnless(bool same, const std::string& call, LSLASTNode* at) diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index a711f5cd2c..1a57c6da97 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -845,7 +845,7 @@ namespace tut template<> template<> void allsltoslua_object::test<23>() { - set_test_name("keys that hold text: link_message's id as SLua passes it, a string; a key given text that is no UUID kept a string; text where SLua takes it, uuid() where only a uuid will do"); + set_test_name("keys that hold text: link_message's id as SLua passes it, a string; a key given text that is no UUID kept a string; text where SLua takes it, ll's key too"); const ALLSLToSLua::Result r = convert("key DOMAIN = \"MY CHANNEL\";\n" "key gOwner;\n" "send(key to) { llMessageLinked(LINK_SET, 0, \"\", to); }\n" @@ -869,7 +869,8 @@ namespace tut has(r, "send(\"other text\")") && has(r, "ll.MessageLinked(LINK_SET, 0, \"\", to)")); ensure("compared as text: " + r.text, has(r, "if id == DOMAIN then") && has(r, "if id == tostring(gOwner) then")); ensure("a key given the id, text: " + r.text, has(r, "local k = id") && has(r, "{k}`)")); - ensure("uuid() where only a uuid will do, said: " + r.text, has(r, "ll.GetOwnerKey(uuid(\"abc\"))") && noted(r, "SluaKeyText")); + ensure("ll's key given text as it is, said: " + r.text, + has(r, "ll.GetOwnerKey(\"abc\" :: any)") && noted(r, "SluaKeyAsText") && !noted(r, "SluaKeyText")); checksClean(r); } @@ -2865,4 +2866,45 @@ namespace tut ensure_equals("unused", unused, std::string("Variable 'found' is never used; prefix with '_' to silence\n")); checksClean(r); } + + template<> template<> + void allsltoslua_object::test<84>() + { + set_test_name("text given where ll or llcompat takes a key is given as it is, as LSL's function took it, `:: any` for the type " + "checker, said once; a UUID written out still a uuid; the script's own function's key still uuid()"); + const std::string lsl = "string gJson;\n" + "integer mine(key who) { return who == llGetOwner(); }\n" + "default { touch_start(integer n) {\n" + " llOwnerSay(\"selected: \" + llKey2Name(llJsonGetValue(gJson, [\"selected\"])));\n" + " llOwnerSay(llKey2Name(\"a2e76fcd-9360-4f6d-a924-\" + \"938f923df11d\"));\n" + " llOwnerSay(llKey2Name(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"));\n" + " if (mine(llJsonGetValue(gJson, [\"owner\"]))) llOwnerSay(\"mine\");\n" + "} }\n"; + const ALLSLToSLua::Result r = convert(lsl); + ensure("Tapple's, as it is: " + r.text, has(r, "ll.Key2Name(ll.JsonGetValue(gJson, {\"selected\"}) :: any)")); + ensure("bracketed where it must be: " + r.text, has(r, "ll.Key2Name((\"a2e76fcd-9360-4f6d-a924-\" .. \"938f923df11d\") :: any)")); + ensure("a UUID written out, a uuid: " + r.text, has(r, "ll.Key2Name(uuid(\"a2e76fcd-9360-4f6d-a924-938f923df11d\"))")); + ensure("the script's own function's key, uuid(): " + r.text, has(r, "mine(uuid(ll.JsonGetValue(gJson, {\"owner\"})))") && + noted(r, "SluaUuidText")); + ensure("said once: " + r.text, count(r, "-- LSL: SLua's ll takes a key as text") == 1); + checksClean(r); + if (!newSolver) + { + // ll's calls alone, strict. + const ALLSLToSLua::Result calls = convert("default { touch_start(integer n) {\n" + " llOwnerSay(llKey2Name(llJsonGetValue(\"{}\", [\"selected\"])));\n" + " llOwnerSay((string)llGetOwnerKey(\"a\" + (string)n));\n" + "} }\n"); + std::string said; + for (const ALScriptProblem& p : service.check("--!strict\n" + calls.text)) + { + said += p.severity == ALScriptProblem::Severity::Error ? p.message + "\n" : std::string(); + } + ensure("strict:\n" + said + "---\n" + calls.text, said.empty() && calls.text.find(":: any") != std::string::npos); + } + + const ALLSLToSLua::Result close = ALLSLToSLua::convert(lsl, ALLSLToSLua::Options::closeToLSL()); + ensure("llcompat's the same: " + close.text, close.converted && close.text.find(":: any)") != std::string::npos); + checksClean(close); + } } diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index e53d95b023..fada75f296 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -1148,6 +1148,7 @@ If you feel this is an error, please contact support@secondlife.com SLua's uuid writes a key's letters small, and finds it the same key as one written small, where LSL's key kept its capitals and was compared as text. ll.DumpList2String writes a vector's or a rotation's parts as LSL did, with five places; SLua's tostring writes as few as each needs, <1, 2, 3>, which tovector reads back the same. uuid() stops the script on text that is no UUID, where LSL's keys held any text; touuid() answers nil instead. + SLua's ll takes a key as text, as LSL's functions did, so text is given as it is; `:: any` tells the type checker, which asks for a uuid. uuid() would stop the script on text that is no UUID. detected[n] is what LSL read with llDetected*(n - 1): SLua counts it from 1, and one past its end is an error, where LSL answered nothing. math.sqrt, math.log and math.log10 answer nan or -inf out of their range, where LSL answered 0 or stopped with a math error. llbase64.decode keeps every byte it decodes, a NUL and what is not UTF-8 among them, where LSL's llBase64ToString cut the string at a NUL and made what was not UTF-8 '?'.