Embedding a new programming language as a module

From AOLserver Wiki
Jump to navigation Jump to search

Embedding a new programming language can actually mean several different things. You may be wanting to write your dynamic pages using this new language as a scripting language, similar to using Tcl as the scripting language for your ADP pages.

Perhaps the easiest first step, though, would be to combine the scripting language of choice with the existing function available to you with your ADP pages. You can do this in a manner very similar to the "First C Module" example, by defining a Tcl function "ns_myfunction" and binding that to calls into your properly initialised scripting language.

For this example, we'll use Lua, a small, simple scripting language that is readily consumed by C programs.

# cat nslua.c

/*
 * Get the data types and constants you will need later on,
 * such as Tcl_Interp and NS_OK.
 */
#include "ns.h"

/*
 * Get data types and functions you will need from lua.
 */
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include <string.h>
#include <sys/types.h>

/*
 * Define the static lua_State that will be the state for this AOLServer
 * module.
 */
static lua_State* L = NULL;

/*
 * Each module asserts its version via the required Ns_ModuleVersion variable
 */
int Ns_ModuleVersion = 1;

/*
 * Declare that this module will implement this initialization method.
 */
int Ns_ModuleInit(char *hServer, char *hModule);

/*
 * Define callback to use to define "ns_lua" to the Tcl interpreter during
 * initialisation.
 */
static int LuaInterpInit(Tcl_Interp *interp, void *context);

/*
 * Declare the C function that will be bound to the "ns_lua" Tcl command.
 */
static int LuaCmd(ClientData context, Tcl_Interp *interp, int argc, char **argv);

/*
 * Implement the Ns_ModuleInit function we declared we would implement earlier.
 * This is called by the server right after the module is loaded. Here, you
 * can read configuration data, initialise internal data, and of course
 * register any Tcl commands.
 */
int Ns_ModuleInit(char *hServer, char *hModule)
{

    /*
     * open the static lua state
     */
    L = lua_open();

    /*
     * Add some standard libraries
     */
    luaopen_base(L);
    luaopen_table(L);
    luaopen_io(L);
    luaopen_string(L);
    luaopen_math(L);

    /*
     * Invoke the server's Tcl interpreter initialisation and pass
     * it the callback we defined to our own interpreter initialisation.
     */
    return (Ns_TclInitInterps(hServer, LuaInterpInit, NULL));

}


/*
 * Implement the interpreter initialisation we declared and passed
 * during module init. Use Tcl_CreateCommand to register
 * the command "ns_lua", passing a callback to "LuaCmd" which
 * we declared earlier.
 */
static int LuaInterpInit(Tcl_Interp *interp, void *context)
{
    Tcl_CreateCommand(interp, "ns_lua", LuaCmd, NULL, NULL);

    return NS_OK;
}


/*
 *
 * Implement the "ns_lua" Tcl command by invoking the lua
 * interpreter using argument 1.
 *
 */
static int LuaCmd(ClientData context, Tcl_Interp *interp, int argc, char **argv)
{
    int i;
    int diffTop;
    int preTop;
    int postTop;
    int error;

    /* TODO: NOT CHECKED THREADSAFE! */

    if (argc != 2)
    {
        Tcl_AppendResult(interp, "wrong # of args: should be ", argv[0], " string", NULL);
        return TCL_ERROR;
    }
    else
    {
        /* because lua functions can add multiple values to the stack */
        preTop = lua_gettop(L);

        error = lua_dostring(L, argv[1]);

        /* we grab before and after stack to determine length */
        postTop = lua_gettop(L);
        diffTop = postTop - preTop;

        /* iterate backwards so that the first result is appended first */
        for (i=diffTop; i>0; i--)
        {
            Tcl_AppendResult(interp, lua_tostring(L,(0-i)), NULL);
            if (i>0)
            {
                Tcl_AppendResult(interp, " ", NULL);
            }
        }

        /* pop all the current results off the lua stack */
        lua_pop(L,diffTop);

        if (error)
        {
            return TCL_ERROR;
        }
        else
        {
            return NS_OK;
        }

    }

}

To compile this module, you'll need to give the AOLServer Makefile.module a bit more information:

# cat Makefile

#
# Module name
#
MOD      =  nslua.so

#
# Objects to build
#
OBJS     =  nslua.o

#
# Header files in THIS directory
#
HDRS     =

#
# Extra libraries
#
MODLIBS  = /usr/lib/liblua50.a /usr/lib/liblualib50.a

#
# Compiler flags
#
CFLAGS   =   -I/usr/include/lua50


include  /usr/lib/aolserver4/Makefile.module

After building the module ('make') you'll see the expected "nslua.so" shared object library. Copy it to your modules directory for your AOLServer installation (/usr/lib/aolserver4/bin for Debian) and take on the typical module loading section in your AOLServer configuration file (/etc/aolserver4/aolserver4.tcl for Debian):

ns_section "ns/server/${servername}/modules"
ns_param   nslua            nslua.so

Finally, re-start your server. It might not be a bad idea to check the log file(s) to ensure that the module loaded and the server started successfully.

Now you are ready to use this module as you would any Tcl function available to your ADP pages:

begin:
<pre>

<%
ns_adp_puts "simple return of multiple values from Lua"
set sLuaResult [ns_lua "return 1,'a'"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "simple return of a single value from Lua"
set sLuaResult [ns_lua "return 2"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "if i is not defined yet, set it to 4"
set sLuaResult [ns_lua "if nil == i then i = 4 end"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "get the current value of i"
set sLuaResult [ns_lua "return i"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "multiple i by 4 (keep reloading the page to see the state of i preserved)"
set sLuaResult [ns_lua "i = i * 4"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "get the current value of i again"
set sLuaResult [ns_lua "return i"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "define a function if it is not already defined"
set sLuaResult [ns_lua {
if nil == f then
  function f(a, b) return a,1,2,3,b end
end
}]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

<%
ns_adp_puts "call the function"
set sLuaResult [ns_lua "return f(19,20)"]
ns_adp_puts "result: $sLuaResult"
ns_adp_puts "end"
%>

</pre>
:end

Likely, though, in the end this isn't what you really wanted. You do have something potentially useful, and you can hack around with Lua amidst the Tcl driving your web pages. But if you don't want Tcl at all, or even if you just want your scripting language's native "print" to go to your HTML output, you'll have to keep going with these tutorials to see how you might add a Lua "handler" on par with the Tcl and ADP handlers that come with AOLServer.

--Caveman 16:32, 5 December 2005 (EST)