Can you redefine the functions used by triggers etc.?

Posted by Tsunami on Wed 07 Jun 2006 01:14 AM — 13 posts, 41,310 views.

USA #0
With lua it is now possible to define functions on the fly. Before I waste a lot of time, can these function be called by a trigger? Ie. something along the lines of:

<trigger
 ...
 script="blahblah"
 ...
>
</trigger>

...

function dostuff()
 doeslotsofstuff...
end

blahblah = dostuff


This is just a very basic example, but I'm looking to potentially define 100's of functions along these lines. Because they are all very similar, it would be one hell of a pain to write them all out, when I can just make a loop to create them all. They need to be able to be called by triggers however. Any ideas?

Thanks
USA #1
If MUSHclient just looks up the function given by the name in the 'script' field and calls it, then there's no reason why this shouldn't work. Functions in Lua are first-class data types which means that you can assign them to variables, etc.

The easiest to confirm this is to just try it out for a very simple example like you have here.
USA #2
Well, having decided to throw away my laziness, I tested it, and it apparently worked. Thanks GAWD, cause I was not looking forward to copy and pasting those 100's of functions...
Australia Forum Administrator #3
The only restriction on this is that MUSHclient checks that the function exists when the trigger is loaded. In this case, function blahblah needs to exist.

However there is nothing stopping you making dummy functions that are subequently replaced.

eg.


function blahblah ()  -- dummy function so trigger loads
end -- function 


-- and later on ...

blahblah = function (name, line, wildcards)
  -- do something different now
end -- function


What MUSHclient does is simply remember whether or not the function exists (not its address) so that later on it can call it. In fact, you can confuse it by deleting a function after it has done its checks.

For example, if a trigger did this:


function blahblah () 
  blahblah = nil   -- function no longer bound to variable blahblah
end -- function 


Then next time the trigger fires you see an error dialog:


attempt to call a nil value
stack traceback:

Amended on Wed 07 Jun 2006 01:44 AM by Nick Gammon
Australia Forum Administrator #4
Another approach would be to have every trigger call the same function, and inside that function, call the "real" function based on the trigger name.

For example:



function trigger1 (name, line, wildcards)
  print "in trigger1"
end -- trigger1

function trigger2 (name, line, wildcards)
  print "in trigger2"
end -- trigger1


triggerfuncs = {
  foo = trigger1, 
  bar = trigger2,
  }
  
function alltriggers (name, line, wildcards)
  local f = triggerfuncs [name]
  if f then
    f (name, line, wildcards)
  end -- function found
end -- alltriggers


Now the trigger whose name (label) is "foo" goes to script trigger1, and trigger whose name is "bar" goes to script trigger2.
USA #5
I was thinking along those lines, but I figured it would be just as much as a pain to create triggers with all those different names and then test for them...

Anyways, here's the real kicker. Suppose I do something like this?

functionname = 'function1'

_G[functionname] = function() ... end


Now, I want, within the body of the created function to call another function with 'function1' as the argument. So the basic problem is finding a way to load the functionname variable into the function scope. Assuming that the functionname variable will no longer be in scope, or will have changed values when the created function is called, is there anyway to do this? As far as I can see, no, but I leave it up to your collective wisdom.
Russia #6
You mean something like:


function test(...) test2(test) end


That shouldn't be a problem, since "test" function is in the global scope, so it is its own upvalue.

Works just the same with function objects explicitely bound to names in the _G table, since it's the same machinery, just under a different disguise.
Australia Forum Administrator #7
I think he means the function name, not a reference to the function itself. I was going to (try to) do an example using upvalues, and will do so tomorrow. :)
Russia #8
I think I get it now. Still not sure though :)

But how about this:



functionname = 'function1'
_G[functionname] = function (tocall) 
  local functionname = functionname
  local name = function()
    tocall(functionname)
  end
  
  return name
end

function testfunc(name) print(name) end

function1 = function1(testfunc)
function1()                       --> "function1"
functionname = "function2"
function1()                       --> "function1"


USA #9
This looks promising, I'll be testing it. Nick was right, I wanted the string value, not function value. I'll post again once I've tested.
USA #10
I've done some testing, and I think I finally get how it works. Took me a bit to bend my brain around it, but I think it should help a great deal. Thanks!
Australia Forum Administrator #11
Yes I was thinking along those lines, but you don't need to use local variables inside the function because arguments are automatically upvalues. Try this:


function makefunc (name, f)
  _G [name] = 
    function (...) 
      f (name, unpack (arg)) 
    end -- local function
end -- makefunc


This function does everything in one hit, adds the function "f" into the global namespace with name "name", and remembers the name as an upvalue, so when "f" is called it is supplied an initial argument, which is the name it was called. Examples:


function test (name, a, b, c)
  print ("in test:", name, a, b, c)
end -- test

function test2 (name, ...)
  print ("in test2:", name, unpack (arg))
end -- test2

makefunc ("Nick", test)
makefunc ("Tsunami", test)
makefunc ("Ked", test2)

Nick ("foo", "bar", "look") --> in test: Nick foo bar look
Tsunami ("x", "y", "z") --> in test: Tsunami x y z
Ked ("ready", "set", "go", "let's", "move", 42) --> in test2: Ked ready set go  let's move  42


You can see from this that the newly created functions, Nick, Tsunami and Ked, all remembered both their own names, and the functions they should call.

The called functions can also be passed arguments. By using "..." and "unpack (arg)" we can pass any number of arguments to the final function.

We can check they are upvalues by using the "showupvalues" function I wrote for another thread:


function showupvalues (f)
  assert (type (f) == "function")
  
  local i = 1
  local name, val
  
  repeat
    name, val = debug.getupvalue (f, i)
    if name then
      print ("index", i, name, "=", val)
      i = i + 1
    end -- if
  until not name

end -- function showupvalues

print (test) --> function: 00325590
showupvalues (Nick)

--> index 1 f = function: 00325590
--> index 2 name  = Nick
Amended on Wed 07 Jun 2006 09:24 PM by Nick Gammon
USA #12
Yup, thats how I ended up doing it after playing around with ways to simplify the code to what I needed. Thanks!