I'm trying to implement "customizable messages" for in "Ketho CombatLog", with the custom message set/provided in an input UI element
Customizable messages, as in, I got X function arguments, and the user can arbitrarily decide the order of the arguments, and which of them should be shown or not:
local someStringToRuleThemAll = "arg3 arg1"
local function test(arg1, arg2, arg3)
if someStringToRuleThemAll == "arg3 arg1" then
print(arg3, arg1)
elseif someStringToRuleThemAll == "arg1 arg3 arg2" then
print(arg1, arg3, arg2)
end
end
So I was thinking of one these two approaches:
Approach 1)loadstring the custom message (I don't have any experience with loadstring)
I'm wondering:
how to pass any arguments/variables into a loadstring function (if possible)
or how to get the "environment" which contains the variables (I don't understand anything yet about environments)
This example seems to work for global variables:
foo = "bar"
local func = loadstring("return foo")
print(func()) [COLOR="DarkGreen"]-- prints "bar"[/COLOR]
Question A: But why does this example not work for upvalue variables? Does it maybe have something to do with the current "environment"?
local foo = "bar"
local func = loadstring("return foo")
print(func()) [COLOR="DarkGreen"]-- prints nil?![/COLOR]
Question B: And why is it the same for function argument variables?
local function test(arg1)
loadstring("print(arg1)")()
end
test("Hello World!") [COLOR="DarkGreen"]-- prints nil?![/COLOR]
Eventually, I had something like this in mind; where a, b, and c are supposed to form the custom message
(.. thinking it over, I guess this example is also just fundamentally wrong )
a = "arg1"
b = "arg5"
c = "arg6"
local function test(arg1, arg2, arg3, arg4, arg5, arg6)
loadstring("print(a, b, c)")()
end
test("Hi", "There", "Mister", "Smith", "Good", "Afternoon")
[COLOR="DarkRed"]-- prints "arg1 arg5 arg6" instead of my intended "Hi Good Afternoon"[/COLOR]
Approach 2)gsub the custom message with the strings passed from my CLEU event handler
local customMsg = "[SOURCE][SPELL] taunted [DEST]"
local function gsubfunc(msg, sourceName, destName, spellName)
msg = msg:gsub("%[SOURCE%]", sourceName)
msg = msg:gsub("%[DEST%]", destName)
msg = msg:gsub("%[SPELL%]", spellName)
return msg
end
print(gsubfunc(customMsg, "[Foo]", "[Bar]", "[Growl]"))
[COLOR="DarkRed"]-- "[Foo][Growl] taunted [Bar]"[/COLOR]
So the other big question is: Am I using loadstring wrong, in that it's not supposed to work like I think it should work? ???
I did try to read the docs on loadstring, but it involved all kinds of stuff with assert, environments, and debugging, which I couldn't apprehend..
Or should I go for gsub instead, or is there a better way to do what I want, or am I just trying to do something unrealistic?
Note 1: I didn't use any dual quoting or escaping quotes in my code snippets, because it didn't seem required
Note 2: I've thought of using string.format, but I'm not sure how this would allow an arbitrarily order of function arguments
Note 3: Is using loadstring "like I think it should work", maybe similar as to how "LuaTexts" works?
The user can set a string in the config that contains certain escape codes, then you can fill a table with all the information with the replacements as the key, pass the table to gsub and Lua does all the replacements for you.
The pattern I use only allows 1 upper or lower case letter for the replacement (ie. %t, %T, %l... etc) but you could change that around.
local customString = "%S %s taunted %D"
local fmtTable = {}
local function replacements(msg, sourceName, destName, spellName)
wipe(fmtTable)
fmtTable.S = sourceName
fmtTable.D = destName
fmtTable.s = spellName
return gsub(customString, "%$([A-Za-z])", fmtTable)
end
This is more simple than creating an environment for your loadstring calls like in LuaTexts. Not sure which approach is the most efficient for a combat log addon... maybe somebody has more insight than me.
Customizable messages, as in, I got X function arguments, and the user can arbitrarily decide the order of the arguments, and which of them should be shown or not:
local someStringToRuleThemAll = "arg3 arg1"
local function test(arg1, arg2, arg3)
if someStringToRuleThemAll == "arg3 arg1" then
print(arg3, arg1)
elseif someStringToRuleThemAll == "arg1 arg3 arg2" then
print(arg1, arg3, arg2)
end
end
You may want to try some of the approaches described here:
Usually, it does not make sense to use loadstring on a literal string. For instance, the code
f = loadstring("i = i + 1")
is roughly equivalent to
f = function () i = i + 1 end
but the second code is much faster, because it is compiled only once, when the chunk is compiled. In the first code, each call to loadstring involves a new compilation. However, the two codes are not completely equivalent, because loadstring does not compile with lexical scoping. To see the difference, let us change the previous examples a little:
local i = 0
f = loadstring("i = i + 1")
g = function () i = i + 1 end
The g function manipulates the local i, as expected, but f manipulates a global i, because loadstring always compiles its strings in a global environment.
Basically it doesn't matter where you call loadstring from, it will always use the global environment (assuming you don't mess with setting the environment or anything) rather than taking into account the environment from which the function is called. That's why your "Question A" and "Question B" code both returned nil.
PS: A simple implementation of assert if your curious what it does:
function assert(something, errorMsg)
if something then
return something
else
if errorMsg then
error(errorMsg)
else
error("whatever the default assert error message is...")
end
end
It's useful for loadstring because if the string you are trying to load into a function causes a complier error, loadstring will return nil as the first value and an error message as the second. Doing loadstring inside an assert will cause any errors to go to the default error handler (ie show up in wow) rather than it just being a silent failure.
The user can set a string in the config that contains certain escape codes, then you can fill a table with all the information with the replacements as the key, pass the table to gsub and Lua does all the replacements for you.
The pattern I use only allows 1 upper or lower case letter for the replacement (ie. %t, %T, %l... etc) but you could change that around.
[...]
This is more simple than creating an environment for your loadstring calls like in LuaTexts. Not sure which approach is the most efficient for a combat log addon... maybe somebody has more insight than me.
I checked the String Interpolation page, and I will try out the first two solutions and also Starinnia's gsub suggestion, as they seem the simplest and most straightforward. I will also try to report back when I'm done with the code from my addon, for anyone to look/comment at ..
Basically it doesn't matter where you call loadstring from, it will always use the global environment (assuming you don't mess with setting the environment or anything) rather than taking into account the environment from which the function is called. That's why your "Question A" and "Question B" code both returned nil.
PS: A simple implementation of assert if your curious what it does:
[...]
It's useful for loadstring because if the string you are trying to load into a function causes a complier error, loadstring will return nil as the first value and an error message as the second. Doing loadstring inside an assert will cause any errors to go to the default error handler (ie show up in wow) rather than it just being a silent failure.
I didn't know it would (always) use the global environment, thanks for explaining that; and also for how/why assert is used with loadstring. I suppose I still have a lot of PIL reading to do
I didn't know it would (always) use the global environment, thanks for explaining that; and also for how/why assert is used with loadstring. I suppose I still have a lot of PIL reading to do
"Global" doesn't always need to mean the same thing. Each function looks in its "global environment" when it's executed, but that environment can be changed from the outside.
GlobalVar = 3
local state = {
GlobalVar = 42,
}
local func,err = loadstring (source_string, "Name of My AddOn")
if func then
setfenv (func, state)
assert(func()) -- can also use pcall, etc
else
print("Error:", err)
-- other error-handling code
end
The routines in 'source_string' are defined at the time func() is run, above. When the code inside those routines is executed later on, they will look inside the 'state' table for their "global" accesses. Anything defined in 'source_string' referring to GlobalVar will see 42 while the rest of the world sees 3, and so forth. This lets you use loadstring without having to litter the real global environment with things you would normally have put into a local variable.
Most such "global" environments are set up to inherit undefined values from the real global, like so:
local state = setmetatable({
-- for purposes of testing, all units are now named Steve
UnitName = function() return "Steve" end,
}, {__index = _G})
This overrides global entries but doesn't restrict their usage. It's not a real sandbox because you can still get to the actual global environment. (That's assuming that "_G" still refers to the actual global environment... if the code doing all this work has itself been loaded into a restricted state, there could be multiple layers of "global" happening.)
I know, but the returned function would accept arguments, which is what the OP was trying to work around.
If userString is "print (y, z, x)" then loadstring would return a function that, given 3 arguments, prints them in the specified order. In other words, rewriting example C:
local function test (...)
local args = {...}
local func = loadstring ("return function (args) print (args[1], args[5], args[6]) end") ()
func (args)
end
test ("Hi", "There", "Mister", "Smith", "Good", "Afternoon")
-- should print the intended "Hi Good Afternoon"
Except you probably want to store the function returned by the function returned by loadstring (func in this example) for efficiency reasons.
Customizable messages, as in, I got X function arguments, and the user can arbitrarily decide the order of the arguments, and which of them should be shown or not:
So I was thinking of one these two approaches:
This example seems to work for global variables:
Question A: But why does this example not work for upvalue variables? Does it maybe have something to do with the current "environment"?Question B: And why is it the same for function argument variables? Eventually, I had something like this in mind; where a, b, and c are supposed to form the custom message
(.. thinking it over, I guess this example is also just fundamentally wrong
I did try to read the docs on loadstring, but it involved all kinds of stuff with assert, environments, and debugging, which I couldn't apprehend..
http://wowprogramming.com/docs/api/loadstring
http://www.lua.org/manual/5.1/manual.html#pdf-loadstring
http://www.lua.org/pil/8.html
Note 1: I didn't use any dual quoting or escaping quotes in my code snippets, because it didn't seem required
Note 2: I've thought of using string.format, but I'm not sure how this would allow an arbitrarily order of function arguments
Note 3: Is using loadstring "like I think it should work", maybe similar as to how "LuaTexts" works?
The user can set a string in the config that contains certain escape codes, then you can fill a table with all the information with the replacements as the key, pass the table to gsub and Lua does all the replacements for you.
The pattern I use only allows 1 upper or lower case letter for the replacement (ie. %t, %T, %l... etc) but you could change that around.
This is more simple than creating an environment for your loadstring calls like in LuaTexts. Not sure which approach is the most efficient for a combat log addon... maybe somebody has more insight than me.
You may want to try some of the approaches described here:
http://lua-users.org/wiki/StringInterpolation
Basically it doesn't matter where you call loadstring from, it will always use the global environment (assuming you don't mess with setting the environment or anything) rather than taking into account the environment from which the function is called. That's why your "Question A" and "Question B" code both returned nil.
PS: A simple implementation of assert if your curious what it does:
It's useful for loadstring because if the string you are trying to load into a function causes a complier error, loadstring will return nil as the first value and an error message as the second. Doing loadstring inside an assert will cause any errors to go to the default error handler (ie show up in wow) rather than it just being a silent failure.
I didn't know it would (always) use the global environment, thanks for explaining that; and also for how/why assert is used with loadstring. I suppose I still have a lot of PIL reading to do
"Global" doesn't always need to mean the same thing. Each function looks in its "global environment" when it's executed, but that environment can be changed from the outside.
The routines in 'source_string' are defined at the time func() is run, above. When the code inside those routines is executed later on, they will look inside the 'state' table for their "global" accesses. Anything defined in 'source_string' referring to GlobalVar will see 42 while the rest of the world sees 3, and so forth. This lets you use loadstring without having to litter the real global environment with things you would normally have put into a local variable.
Most such "global" environments are set up to inherit undefined values from the real global, like so:
This overrides global entries but doesn't restrict their usage. It's not a real sandbox because you can still get to the actual global environment. (That's assuming that "_G" still refers to the actual global environment... if the code doing all this work has itself been loaded into a restricted state, there could be multiple layers of "global" happening.)
loadstring ("return function(x,y,z) " .. userString .. " end")
And then evaluate it once to get the function you need?
If userString is "print (y, z, x)" then loadstring would return a function that, given 3 arguments, prints them in the specified order. In other words, rewriting example C:
Except you probably want to store the function returned by the function returned by loadstring (func in this example) for efficiency reasons.