Go Back   WowAce Forums > General > Lua Code Discussion
Lua Code Discussion You scared? Terrified. Mortified. Petrified. Stupefied... by [coding].

Reply
 
Thread Tools
Old 08-01-2011   #1
Ketho
Senior Member
 
Ketho's Avatar
 
Join Date: Dec 2008
Location: The Netherlands
Posts: 258
Default loadstring for in "customizable messages"

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:
Code:
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:
    Code:
    foo = "bar"
    local func = loadstring("return foo")
    print(func()) -- prints "bar"
    Question A: But why does this example not work for upvalue variables? Does it maybe have something to do with the current "environment"?
    Code:
    local foo = "bar"
    local func = loadstring("return foo")
    print(func()) -- prints nil?!
    Question B: And why is it the same for function argument variables?
    Code:
    local function test(arg1)
    	loadstring("print(arg1)")()
    end
    
    test("Hello World!") -- prints nil?!
    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 )
    Code:
    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")
    -- prints "arg1 arg5 arg6" instead of my intended "Hi Good Afternoon"
  • Approach 2) gsub the custom message with the strings passed from my CLEU event handler
    Code:
    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]"))
    -- "[Foo][Growl] taunted [Bar]"
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..
http://www.wowpedia.org/API_loadstring
http://wowprogramming.com/docs/api/loadstring
http://www.lua.org/manual/5.1/manual...pdf-loadstring
http://www.lua.org/pil/8.html
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?
__________________

Last edited by Ketho; 08-01-2011 at 11:40 PM.
Ketho is offline   Reply With Quote
Old 08-02-2011   #2
Starinnia
Hero Member
 
Starinnia's Avatar
 
Join Date: Jun 2006
Location: Chicago
Posts: 654
Default Re: loadstring for in "customizable messages"

I went the gsub route in RealIDToons.

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.
Code:
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.
__________________
Author of SimpleMD, MobileVault, MillHelp, Sifter, ComboPointsRedux, RealID Toons, and Prescription.
Starinnia is offline   Reply With Quote
Old 08-02-2011   #3
Farmbuyer
Amazing Member
 
Farmbuyer's Avatar
 
Join Date: Feb 2005
Posts: 1,110
Default Re: loadstring for in "customizable messages"

Quote:
Originally Posted by Ketho View Post
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:
Code:
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:

http://lua-users.org/wiki/StringInterpolation
__________________
In wizardry, one must often be willing to consider serendipitous events as unqualified successes. -Vaarsuvius
Farmbuyer is offline   Reply With Quote
Old 08-02-2011   #4
sapu94
Full Member
 
Join Date: Feb 2010
Posts: 107
Default Re: loadstring for in "customizable messages"

Quote:
Originally Posted by http://www.lua.org/pil/8.html
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:

Code:
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.
__________________
Lead developer of the TradeSkillMaster gold making addon.

"A good programmer is someone who always looks both ways before crossing a one-way street." ~Doug Linder

Last edited by sapu94; 08-02-2011 at 03:58 AM.
sapu94 is offline   Reply With Quote
Old 08-02-2011   #5
Ketho
Senior Member
 
Ketho's Avatar
 
Join Date: Dec 2008
Location: The Netherlands
Posts: 258
Default Re: loadstring for in "customizable messages"

Quote:
Originally Posted by Starinnia View Post
I went the gsub route in RealIDToons.

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.
Quote:
Originally Posted by Farmbuyer View Post
You may want to try some of the approaches described here:

http://lua-users.org/wiki/StringInterpolation
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 ..
Quote:
Originally Posted by sapu94 View Post
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
__________________
Ketho is offline   Reply With Quote
Old 08-02-2011   #6
Farmbuyer
Amazing Member
 
Farmbuyer's Avatar
 
Join Date: Feb 2005
Posts: 1,110
Default Re: loadstring for in "customizable messages"

Quote:
Originally Posted by Ketho View Post
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.

Code:
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:

Code:
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.)
__________________
In wizardry, one must often be willing to consider serendipitous events as unqualified successes. -Vaarsuvius
Farmbuyer is offline   Reply With Quote
Old 08-09-2011   #7
Iroared
Member
 
Join Date: Feb 2009
Posts: 49
Default Re: loadstring for in "customizable messages"

Couldn't you do

loadstring ("return function(x,y,z) " .. userString .. " end")

And then evaluate it once to get the function you need?
Iroared is offline   Reply With Quote
Old 08-09-2011   #8
egingell
Amazing Member
 
egingell's Avatar
 
Join Date: May 2006
Location: Cenarion Circle
Posts: 1,582
Send a message via AIM to egingell Send a message via MSN to egingell
Default Re: loadstring for in "customizable messages"

FYI: loadstring() returns a function, so your code will return a function that creates and returns a function.
__________________
WoWWiki has moved! Please, make a note of it.
My Addons (a handy dandy list).
My Characters (yes, I have a guild with only me in it).
egingell is offline   Reply With Quote
Old 08-09-2011   #9
Iroared
Member
 
Join Date: Feb 2009
Posts: 49
Default Re: loadstring for in "customizable messages"

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:

Code:
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.

Last edited by Iroared; 08-09-2011 at 03:45 PM.
Iroared is offline   Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT. The time now is 06:53 PM.