Lua Patch

From Gwen Morse's Wiki
Jump to: navigation, search

How to patch:

tar -xzvf tf-50b8

cd tf-50b8

patch -p2 < ../add-lua.patch

Assuming the patch goes in ok... You now need to run autoconf!!!!! THEN do...

./configure --enable-lua --prefix=/usr

make

sudo make install

diff --git a/tf/configure.in b/tf/configure.in
index ffed268..fc875ce 100644
--- a/tf/configure.in
+++ b/tf/configure.in
@@ -85,6 +85,9 @@ AC_ARG_ENABLE(process,
 AC_ARG_ENABLE(float,
 [  --disable-float         disable floating point arithmetic and functions],
     , enable_float=yes)
+AC_ARG_ENABLE(lua,
+[  --enable-lua            enable LUA scripting],
+    enable_lua=yes, enable_lua=no)
 
 AC_ARG_WITH(incdirs,
 [  --with-incdirs=DIRS     search for include files in DIRS])
@@ -714,6 +717,21 @@ AC_SUBST(CPPFLAGS)
 AC_SUBST(LIBS)
 AC_SUBST(OTHER_OBJS)
 
+if test "$enable_lua" = "yes"; then
+	AC_MSG_NOTICE([LUA scripting enabled.])
+	LUA_DEFINES="-DLUA_ENABLED"
+	LUA_LIBS="-llua"
+	LUA_SOURCES="lua.c"
+	LUA_OBJECTS="lua.$OBJEXT"
+else
+	AC_MSG_NOTICE([LUA scripting disabled.])
+fi
+
+AC_SUBST(LUA_DEFINES)
+AC_SUBST(LUA_LIBS)
+AC_SUBST(LUA_SOURCES)
+AC_SUBST(LUA_OBJECTS)
+
 AC_OUTPUT(Makefile unix/vars.mak src/tfdefs.h, [
 
 ### Create src/Makefile from pieces.
diff --git a/tf/lua/first.lua b/tf/lua/first.lua
new file mode 100644
index 0000000..824fe20
--- /dev/null
+++ b/tf/lua/first.lua
@@ -0,0 +1,108 @@
+-- fh = io.open("/home/goblin/LUA", "w")
+-- fh:write("otworzone\n")
+
+    ---------------------------------------------
+    -- Return indentation string for passed level
+    ---------------------------------------------
+    local function tabs(i)
+        return string.rep(".",i).." "   -- Dots followed by a space
+    end
+
+    -----------------------------------------------------------
+    -- Return string representation of parameter's value & type
+    -----------------------------------------------------------
+    local function toStrType(t)
+        local function fttu2hex(t) -- Grab hex value from tostring() output
+            local str = tostring(t);
+            if str == nil then
+                return "tostring() failure! \n"
+            else
+                local str2 = string.match(str,"[ :][ (](%x+)")
+                if str2 == nil then
+                    return "string.match() failure: "..str.."\n"
+                else
+                    return "0x"..str2
+                end
+            end
+        end
+        -- Stringify a value of a given type using a table of functions keyed
+        -- by the name of the type (Lua's version of C's switch() statement).
+        local stringify = {
+            -- Keys are all possible strings that type() may return,
+            -- per http://www.lua.org/manual/5.1/manual.html#pdf-type.
+            ["nil"]			= function(v) return "nil (nil)"			    end,
+            ["string"]		= function(v) return '"'..v..'" (string)'	    end,
+            ["number"]		= function(v) return v.." (number)"			    end,
+            ["boolean"]		= function(v) return tostring(v).." (boolean)"  end,
+            ["function"]	= function(v) return fttu2hex(v).." (function)" end,
+            ["table"]		= function(v) return fttu2hex(v).." (table)"	end,
+            ["thread"]		= function(v) return fttu2hex(v).." (thread)"	end,
+            ["userdata"]	= function(v) return fttu2hex(v).." (userdata)" end
+        }
+        return stringify[type(t)](t)
+    end
+
+    -------------------------------------
+    -- Count elements in the passed table
+    -------------------------------------
+    local function lenTable(t)		-- What Lua builtin does this simple thing?
+        local n=0                   -- '#' doesn't work with mixed key types
+        if ("table" == type(t)) then
+            for key in pairs(t) do  -- Just count 'em
+                n = n + 1
+            end
+            return n
+        else
+            return nil
+        end
+    end
+
+    --------------------------------
+    -- Pretty-print the passed table
+    --------------------------------
+    local function do_dumptable(t, indent, seen)
+        -- "seen" is an initially empty table used to track all tables
+        -- that have been dumped so far.  No table is dumped twice.
+        -- This also keeps the code from following self-referential loops,
+        -- the need for which was found when first dumping "_G".
+		local ret = ""
+        if ("table" == type(t)) then	-- Dump passed table
+            seen[t] = 1
+            if (indent == 0) then
+                ret = ret .. "The passed table has "..lenTable(t).." entries:"
+                indent = 1
+            end
+            for f,v in pairs(t) do
+                if ("table" == type(v)) and (seen[v] == nil) then    -- Recurse
+                    ret = ret ..  tabs(indent)..toStrType(f).." has "..lenTable(v).." entries: {"
+                    do_dumptable(v, indent+1, seen)
+                    ret = ret .. tabs(indent).."}" 
+                else
+                    ret = ret .. tabs(indent)..toStrType(f).." = "..toStrType(v)
+                end
+            end
+        else
+            ret = ret .. tabs(indent).."Not a table!"
+        end
+
+		return ret
+    end
+
+    --------------------------------
+    -- Wrapper to handle persistence
+    --------------------------------
+    function dumptable(t)   -- Only global declaration in the package
+        -- This wrapper exists only to set the environment for the first run:
+        -- The second param is the indentation level.
+        -- The third param is the list of tables dumped during this call.
+        -- Getting this list allocated and freed was a pain, and this
+        -- wrapper was the best solution I came up with...
+        return do_dumptable(t, 0, {})
+    end
+
+
+
+function luadupa(str, attrs, line_attr)
+	-- fh:write("dodaje: str: " .. str .. "j"\n")
+	tf_eval("/echo " .. str .. "] " .. dumptable(attrs) .. "> " .. line_attr)
+end
diff --git a/tf/src/cmdlist.h b/tf/src/cmdlist.h
index 95520de..8b8b322 100644
--- a/tf/src/cmdlist.h
+++ b/tf/src/cmdlist.h
@@ -31,6 +31,9 @@
 /*     name            function                     reserved? */
 defcmd("BEEP"        , handle_beep_command        , 0)
 defcmd("BIND"        , handle_bind_command        , 0)
+#if LUA_ENABLED
+defcmd("CALLLUA"     , handle_calllua_command    , 0)
+#endif
 defcmd("CONNECT"     , handle_connect_command     , 0)
 defcmd("CORE"        , handle_core_command        , 0)
 defcmd("DC"          , handle_dc_command          , 0)
@@ -58,10 +61,16 @@ defcmd("LISTSTREAMS" , handle_liststreams_command , 0)
 defcmd("LISTVAR"     , handle_listvar_command     , 0)
 defcmd("LISTWORLDS"  , handle_listworlds_command  , 0)
 defcmd("LOAD"        , handle_load_command        , 0)
+#if LUA_ENABLED
+defcmd("LOADLUA"     , handle_loadlua_command    , 0)
+#endif
 defcmd("LOCALECHO"   , handle_localecho_command   , 0)
 defcmd("LOG"         , handle_log_command         , 0)
 defcmd("PS"          , handle_ps_command          , 0)
 defcmd("PURGE"       , handle_purge_command       , 0)
+#if LUA_ENABLED
+defcmd("PURGELUA"    , handle_purgelua_command    , 0)
+#endif
 defcmd("QUIT"        , handle_quit_command        , 0)
 defcmd("QUOTE"       , handle_quote_command       , 0)
 defcmd("RECALL"      , handle_recall_command      , 0)
diff --git a/tf/src/command.c b/tf/src/command.c
index 0409787..67a1aa5 100644
--- a/tf/src/command.c
+++ b/tf/src/command.c
@@ -30,6 +30,7 @@ static const char RCSid[] = "$Id: command.c,v 35004.141 2007/01/13 23:12:39 kkey
 #include "expand.h"     /* macro_run() */
 #include "signals.h"    /* suspend(), shell() */
 #include "variable.h"
+#include "lua.h" /* lua scripting handling */
 
 int exiting = 0;
 
diff --git a/tf/src/lua.c b/tf/src/lua.c
new file mode 100644
index 0000000..3c9439d
--- /dev/null
+++ b/tf/src/lua.c
@@ -0,0 +1,136 @@
+#include <string.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include "lua.h"
+
+#define myerror(str) eputs(str)
+#define myinfo(str) oputs(str)
+
+// it's the function that Lua scripts will see as "tfexec"
+static int tfeval_for_lua(lua_State *state)
+{
+	String *func;
+	const char *arg = luaL_checkstring(state, 1);
+
+	(func = Stringnew(NULL, 0, 0))->links++;
+    Sprintf(func, arg);
+	handle_eval_command(func, 0);
+    
+	Stringfree(func);
+
+	return 0;
+}
+
+lua_State *lua_state = NULL;
+
+struct Value *handle_calllua_command(String *args, int offset)
+{
+	// for the love of <enter_your_gods_name_here>,
+	// REMEMBER TO FREE cstr_args before returning!
+	char *cstr_args = strdup(args->data + offset);
+	char *func, *tmp;
+	int argpos=-1;
+	int num_args;
+
+	if(lua_state == NULL)
+	{
+		myerror("no script loaded");
+		free(cstr_args);
+		return newint(1);
+	}
+
+	func = cstr_args;
+	tmp = strchr(cstr_args, ' ');
+	if(tmp != NULL)
+	{
+		*tmp = '\0';
+		argpos = tmp - func + 1;
+	}
+
+	lua_getfield(lua_state, LUA_GLOBALSINDEX, func);
+	if(lua_isfunction(lua_state, -1) != 1)
+	{
+		myerror("no such function");
+		lua_pop(lua_state, 1);
+		free(cstr_args);
+		return newint(1);
+	}
+
+	if(argpos > -1)
+	{
+		int i, table_i=0;
+
+		// first argument: the string data
+		lua_pushstring(lua_state, func + argpos);
+
+		// second arg: the attributes of that string as an array of ints
+		lua_newtable(lua_state);
+
+		if(args->charattrs)
+		{
+			for(i=argpos; i<args->len; i++)
+			{
+				lua_pushinteger(lua_state, args->charattrs[i]);
+				lua_rawseti(lua_state, -2, table_i++);
+			}
+		}
+
+		// third arg: attributes for the whole line
+		lua_pushinteger(lua_state, args->attrs);
+
+		num_args = 3;
+	}
+	else
+		num_args = 0;
+	
+	if(lua_pcall(lua_state, num_args, 0, 0) != 0)
+	{
+		myerror(lua_tostring(lua_state, -1));
+		// pop the error message
+		lua_pop(lua_state, 1);
+		free(cstr_args);
+		return newint(1);
+	}
+
+	free(cstr_args);
+	return newint(0);
+}
+
+struct Value *handle_loadlua_command(String *args, int offset)
+{
+	// check if lua has been initialised
+	if(lua_state == NULL)
+	{ // it hasn't: initialise now
+		lua_state = lua_open();
+		luaL_openlibs(lua_state);
+
+		// add our tf_eval() function for Lua scripts to call
+		lua_pushcfunction(lua_state, tfeval_for_lua);
+		lua_setglobal(lua_state, "tf_eval");
+	}
+
+	// now read and execute the scripts that user asked for
+	if(luaL_dofile(lua_state, args->data + offset) != 0)
+	{
+		myerror("cannot load script:");
+		myerror(lua_tostring(lua_state, -1));
+		lua_pop(lua_state, 1);
+	}
+	else
+		myinfo("loaded");
+
+	return newint(0);
+}
+
+struct Value *handle_purgelua_command(String *args, int offset)
+{
+	lua_close(lua_state);
+	lua_state = NULL;
+
+	myinfo("all scripts forgotten");
+
+	return newint(0);
+}
diff --git a/tf/src/lua.h b/tf/src/lua.h
new file mode 100644
index 0000000..2c1e423
--- /dev/null
+++ b/tf/src/lua.h
@@ -0,0 +1,29 @@
+#ifndef LUA_H
+#define LUA_H
+
+#include "tfconfig.h"
+#include "port.h"
+#include "tf.h"
+#include "util.h"
+#include "pattern.h"
+#include "search.h"
+#include "tfio.h"
+#include "cmdlist.h"
+#include "command.h"
+#include "world.h"	/* World, find_world() */
+#include "socket.h"	/* openworld() */
+#include "output.h"	/* oflush(), dobell() */
+#include "attr.h"
+#include "macro.h"
+#include "keyboard.h"	/* find_key(), find_efunc() */
+#include "expand.h"     /* macro_run() */
+#include "signals.h"    /* suspend(), shell() */
+#include "variable.h"
+
+/* that's definitely too many headers */
+
+struct Value *handle_calllua_command(String *args, int offset);
+struct Value *handle_loadlua_command(String *args, int offset);
+struct Value *handle_purgelua_command(String *args, int offset);
+
+#endif 
diff --git a/tf/src/tfio.c b/tf/src/tfio.c
index 2cd2103..038d4f6 100644
--- a/tf/src/tfio.c
+++ b/tf/src/tfio.c
@@ -518,11 +518,14 @@ void vSprintf(String *buf, int flags, const char *fmt, va_list ap)
         *++specptr = '\0';
 
         switch (*fmt) {
+			va_list ap_copy;
         case 'd': case 'i':
         case 'x': case 'X': case 'u': case 'o':
         case 'f': case 'e': case 'E': case 'g': case 'G':
         case 'p':
-            vsprintf(tempbuf, spec, ap);
+			va_copy(ap_copy, ap);
+            vsprintf(tempbuf, spec, ap_copy);
+			va_end(ap_copy);
             Stringcat(buf, tempbuf);
             /* eat the arguments used by vsprintf() */
             while (stars--) (void)va_arg(ap, int);
diff --git a/tf/src/vars.mak b/tf/src/vars.mak
index ef4e85d..00a3b36 100644
--- a/tf/src/vars.mak
+++ b/tf/src/vars.mak
@@ -20,10 +20,11 @@ TFVER=50b8
 
 SOURCE = attr.c command.c dstring.c expand.c expr.c help.c history.c \
   keyboard.c macro.c main.c malloc.c output.c process.c search.c \
-  signals.c socket.c tfio.c tty.c util.c variable.c world.c
+  signals.c socket.c tfio.c tty.c util.c variable.c world.c \
+  $(LUA_SOURCES)
 
 OBJS = attr.$O command.$O dstring.$O expand.$O expr.$O help.$O history.$O \
   keyboard.$O macro.$O main.$O malloc.$O output.$O pattern.$O process.$O \
   search.$O signals.$O socket.$O tfio.$O tty.$O util.$O variable.$O world.$O \
-  $(OTHER_OBJS)
+  $(LUA_OBJECTS) $(OTHER_OBJS)
 
diff --git a/tf/tf-lib/tf-help b/tf/tf-lib/tf-help
index 1ffc37f..3e7cc8c 100644
--- a/tf/tf-lib/tf-help
+++ b/tf/tf-lib/tf-help
@@ -10785,4 +10785,62 @@ worlds
 
   See also: �[1msockets�[22;0m 
 
+&/loadlua
+
+/loadlua <filename>
+
+  Loads and interprets a Lua script given in <filename>. All global
+  functions defined in the script can be called with /calllua.
+
+&/calllua
+
+/calllua <function> <argument>
+
+  Calls a Lua function. It needs to be already defined in one of the previously
+  loaded Lua scripts (using /loadlua).
+
+  Everything entered after a function name and a space is treated as a single
+  string argument.
+
+  Information on writing Lua scripts can be found in /help lua.
+
+&/purgelua
+
+/purgelua
+
+  Forgets all Lua scripts loaded. You won't be able to use /calllua afterwards
+  until you load some scripts.
+
+&lua
+
+1. Lua functions
+   The function called by /calllua can take up to 3 arguments and shouldn't
+   return any values. The arguments are as follows:
+
+    1st: the string passed after /calllua <function> (the plain argument)
+	2nd: a table of integers, where index corresponds to the characters
+	     in 1st argument. The integers define attributes (such as colour)
+		 of characters in the 1st argument. Useful to get the colours when
+		 defining triggers. Can be an empty array, e.g. if the string was
+		 entered by the user.
+	3rd: an integer describing attributes for the whole line. I mostly found
+	     it to be 0, I don't know if it can be something different.
+
+2. Passing input to Lua functions
+   The way to inform a Lua script about what the MUD has sent to you is simple:
+
+     /def -t'*' call_lua_parser = /calllua <parser_func> %{*}
+
+   You should write an appropriate <parser_func> in your scripts that will
+   take the input line and do what it likes to do with it.
+
+3. Calling tf commands from Lua functions
+   A symbol will be defined in your Lua functions named tf_eval. Just call
+   tf_eval with a single string attribute. This will have the exact same effect
+   as typing /eval <string> in the tf's prompt. Example:
+
+   function foo(str, attrs, line_attr)
+     tf_eval("/echo Hello world")
+   end
+
 &
diff --git a/tf/unix/vars.mak.in b/tf/unix/vars.mak.in
index 039fb7c..71ec73d 100644
--- a/tf/unix/vars.mak.in
+++ b/tf/unix/vars.mak.in
@@ -32,11 +32,14 @@ SYMLINK    = @SYMLINK@
 # MANTYPE    = @MANTYPE@
 MODE       = @MODE@
 CC         = @CC@
-CFLAGS     = @CFLAGS@ @CPPFLAGS@
-LDFLAGS    = @LDFLAGS@
+CFLAGS     = @CFLAGS@ @CPPFLAGS@ @LUA_DEFINES@
+LDFLAGS    = @LDFLAGS@ 
 STD_C      = @STD_C@
 STRIP      = @STRIP@
 RANLIB     = @RANLIB@
-LIBS       = @LIBS@
+LIBS       = @LIBS@ @LUA_LIBS@
 OTHER_OBJS = @OTHER_OBJS@
 PCRE       = @PCRE@
+
+LUA_SOURCES = @LUA_SOURCES@
+LUA_OBJECTS = @LUA_OBJECTS@