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 '?'.