Fun to Program – Embedded Lua

Date: 2013/08/20 (initial publish), 2021/08/02 (last update)

Source: en/fun2-00020.md

Previous Post Top Next Post

TOC

This was originally written and created around 2013 and may require to be updated. (2021)

Embedded Lua

The Lua interpreter embedded into a small C program offers an ideal configuration system. It can execute any functions of the hosting C program under specified conditions and sequences with arbitrary parameter values.

Learn Lua:

In Debian, Lua interpreter is offered as a shared library linkable from any C codes.

Pseudo stack

The hosting C program and the embedded Lua interpreter needs to communicate each other. This communication is done by passing parameters via the shared Lua pseudo stack. Here is a private library function to print out its content.

dump.c defining the dump_stack(lua_State* L) function source

#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

void dump_stack(lua_State* L)
{
    int i;
    int stack_size = lua_gettop(L); /*  Lua virtual stack size */
    printf ("=== DUMP STACK top=[%i] --> bottom=[1] ===\n", stack_size);
    for (i = stack_size; i >= 1; i--) {
    	int type = lua_type(L, i);
    	printf("C: stack[%2i or %3i] type=%10s", i, i - stack_size - 1, 
    			lua_typename(L,type));
    	switch(type) {
    	case LUA_TNUMBER: /* Lua type: number */
    		printf(";  value= %g", lua_tonumber(L, i));
    		break;
    	case LUA_TBOOLEAN: /* Lua type: boolean */
    		if (lua_toboolean(L, i)) {
    			printf(";  value= true");
    		} else {
    			printf(";  value= false");
    		}
    		break;
    	case LUA_TSTRING: /* Lua type: string */
    		printf(";  value= %s", lua_tostring(L, i) );
    		break;
    	case LUA_TNIL: /* Lua type: nil */
    			printf(";  value= nil (nil == 0)");
    		break;
    	default:
    			printf(";  value= non-printable");
    		break;
    	}
    	printf("\n");
    }
}

dump.h defining the dump_stack(lua_State* L) function header

void dump_stack(lua_State* L);

Here is an example hosting C program of Lua interpreter luastack.c. This manipulates the shared parameter passing pseudo stack from the hosting C program.

The hosting luastack.c source

#include <stdlib.h>
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "dump.h"

#define TRUE 1
#define FALSE 0

int 
main (void)
{
    lua_State* L = luaL_newstate();

    printf("lua_pushboolean(L, TRUE)\n");
    lua_pushboolean(L, TRUE);
    dump_stack(L);

    printf("lua_pushnumber(L, 3.141592) -- push 3.141592\n");
    lua_pushnumber(L, 3.141592);
    dump_stack(L);

    printf("lua_pushinteger(L, -200) -- push -200\n");
    lua_pushinteger(L, -200);
    dump_stack(L);

    printf("lua_pushnil(L) -- push nil\n");
    lua_pushnil(L);
    dump_stack(L);

    printf("lua_pushstring(L, \"Hello world!\") -- push \"Hello world!\"\n");
    lua_pushstring(L, "Hello world!");
    dump_stack(L);

    printf("lua_replace(L, 2) -- replace stack[2] with stack top\n");
    lua_replace(L, 2);
    dump_stack(L);
    
    printf("lua_pushvalue(L, 1) -- push value of stack[1]\n");
    lua_pushvalue(L, 1);
    dump_stack(L);
    
    printf("lua_pushvalue(L, -2) -- push value of stack[-2]\n");
    lua_pushvalue(L, -2);
    dump_stack(L);
    
    printf("lua_insert(L, 2) -- insert stack top to stack[2]\n");
    lua_insert(L, 2);
    dump_stack(L);
    
    printf("lua_remove(L, 2) -- remove stack[2]\n");
    lua_remove(L, 2);
    dump_stack(L);
    
    printf("lua_settop(L, -1) -- remove 1 item from stack top\n");
    lua_settop(L, -1);
    dump_stack(L);

    printf("\n");

    lua_close(L);
    return EXIT_SUCCESS;
}

Let’s compile these and run luastack.

Compile and run of luastack

$ gcc -Wall `pkg-config --cflags --libs lua5.1` -c dump.c
$ gcc -Wall `pkg-config --cflags --libs lua5.1` -c luastack.c
$ gcc -Wall `pkg-config --cflags --libs lua5.1` dump.o luastack.o -o luastack
$ ./luastack
lua_pushboolean(L, TRUE)
=== DUMP STACK top=[1] --> bottom=[1] ===
C: stack[ 1 or  -1] type=   boolean;  value= true
lua_pushnumber(L, 3.141592) -- push 3.141592
=== DUMP STACK top=[2] --> bottom=[1] ===
C: stack[ 2 or  -1] type=    number;  value= 3.14159
C: stack[ 1 or  -2] type=   boolean;  value= true
lua_pushinteger(L, -200) -- push -200
=== DUMP STACK top=[3] --> bottom=[1] ===
C: stack[ 3 or  -1] type=    number;  value= -200
C: stack[ 2 or  -2] type=    number;  value= 3.14159
C: stack[ 1 or  -3] type=   boolean;  value= true
lua_pushnil(L) -- push nil
=== DUMP STACK top=[4] --> bottom=[1] ===
C: stack[ 4 or  -1] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -2] type=    number;  value= -200
C: stack[ 2 or  -3] type=    number;  value= 3.14159
C: stack[ 1 or  -4] type=   boolean;  value= true
lua_pushstring(L, "Hello world!") -- push "Hello world!"
=== DUMP STACK top=[5] --> bottom=[1] ===
C: stack[ 5 or  -1] type=    string;  value= Hello world!
C: stack[ 4 or  -2] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -3] type=    number;  value= -200
C: stack[ 2 or  -4] type=    number;  value= 3.14159
C: stack[ 1 or  -5] type=   boolean;  value= true
lua_replace(L, 2) -- replace stack[2] with stack top
=== DUMP STACK top=[4] --> bottom=[1] ===
C: stack[ 4 or  -1] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -2] type=    number;  value= -200
C: stack[ 2 or  -3] type=    string;  value= Hello world!
C: stack[ 1 or  -4] type=   boolean;  value= true
lua_pushvalue(L, 1) -- push value of stack[1]
=== DUMP STACK top=[5] --> bottom=[1] ===
C: stack[ 5 or  -1] type=   boolean;  value= true
C: stack[ 4 or  -2] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -3] type=    number;  value= -200
C: stack[ 2 or  -4] type=    string;  value= Hello world!
C: stack[ 1 or  -5] type=   boolean;  value= true
lua_pushvalue(L, -2) -- push value of stack[-2]
=== DUMP STACK top=[6] --> bottom=[1] ===
C: stack[ 6 or  -1] type=       nil;  value= nil (nil == 0)
C: stack[ 5 or  -2] type=   boolean;  value= true
C: stack[ 4 or  -3] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -4] type=    number;  value= -200
C: stack[ 2 or  -5] type=    string;  value= Hello world!
C: stack[ 1 or  -6] type=   boolean;  value= true
lua_insert(L, 2) -- insert stack top to stack[2]
=== DUMP STACK top=[6] --> bottom=[1] ===
C: stack[ 6 or  -1] type=   boolean;  value= true
C: stack[ 5 or  -2] type=       nil;  value= nil (nil == 0)
C: stack[ 4 or  -3] type=    number;  value= -200
C: stack[ 3 or  -4] type=    string;  value= Hello world!
C: stack[ 2 or  -5] type=       nil;  value= nil (nil == 0)
C: stack[ 1 or  -6] type=   boolean;  value= true
lua_remove(L, 2) -- remove stack[2]
=== DUMP STACK top=[5] --> bottom=[1] ===
C: stack[ 5 or  -1] type=   boolean;  value= true
C: stack[ 4 or  -2] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -3] type=    number;  value= -200
C: stack[ 2 or  -4] type=    string;  value= Hello world!
C: stack[ 1 or  -5] type=   boolean;  value= true
lua_settop(L, -1) -- remove 1 item from stack top
=== DUMP STACK top=[5] --> bottom=[1] ===
C: stack[ 5 or  -1] type=   boolean;  value= true
C: stack[ 4 or  -2] type=       nil;  value= nil (nil == 0)
C: stack[ 3 or  -3] type=    number;  value= -200
C: stack[ 2 or  -4] type=    string;  value= Hello world!
C: stack[ 1 or  -5] type=   boolean;  value= true

C + Lua

Let’s write a hosting C program luahost.c which can load a Lua script script.lua into the embedded Lua interpreter.

luahost.c source

#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "dump.h"
#include "average.h"

#define TRUE 1
#define FALSE 0

int
main(int argc, char *argv[])
{
    double w;
    double h;
    double a;

    puts("-- lua_State* L = luaL_newstate(); /* initialize Lua */");
    lua_State* L = luaL_newstate();

    puts("-- luaL_openlibs(L); /* standard libraries */");
    luaL_openlibs(L);

    puts("-- lua_register(L, \"average\", c_average); /* private library */");
    lua_register(L, "average", c_average);

    puts("-- luaL_loadfile(L, argv[1]) ...");
    if (luaL_loadfile(L, argv[1]) || lua_pcall(L, 0, 0, 0))
    {
    	printf("cannot run: %s\n", lua_tostring(L, -1));
    	return EXIT_FAILURE;
    }
    else
    	printf("-- %s load and executed.\n", argv[1]);

    puts("-- lua_getglobal(L, \"width\"); /* push Lua variable width */");
    lua_getglobal(L, "width");
    dump_stack(L);

    puts("-- lua_getglobal(L, \"height\"); /* push Lua variable height */");
    lua_getglobal(L, "height");
    dump_stack(L);

    puts("-- lua_getglobal(L, \"ave\"); /* push Lua variable ave */");
    lua_getglobal(L, "ave");
    dump_stack(L);

    puts("-- a = lua_tonumber(L, -1); /* read ave at -1 in the stack */");
    a = lua_tonumber(L, -1);
    printf("C: Average = %g\n", a);

    puts("-- h = lua_tonumber(L, -2); /* read height at -2 in the stack */");
    h = lua_tonumber(L, -2);
    printf("C: Height  = %g\n", h);

    puts("-- h = lua_tonumber(L, -3); /* read width at -3 in the stack */");
    w = lua_tonumber(L, -3);
    printf("C: Width   = %g\n", w);

    printf("C: Volume  %g * %g * %g = %g\n", w, h, a, w*h*a);

    lua_close(L);
    return EXIT_SUCCESS;
}

script.lua source

-- lua example

ave = average(1.0, 2.0, 3.0, 4.0)

print(string.format("Lua: ave    = %g", ave))

if os.getenv("SMALL") == "YES" then
    width = 5
    height = 50
else
    width = 100
    height = 200
end

print(string.format("Lua: width  = %g", width))
print(string.format("Lua: height = %g", height))

Let’s see how they interact.

Compile and run of luahost

$ gcc -Wall `pkg-config --cflags --libs lua5.1` -c average.c
$ gcc -Wall `pkg-config --cflags --libs lua5.1` -c luahost.c
$ gcc -Wall `pkg-config --cflags --libs lua5.1` dump.o average.o \
    luahost.o -o luahost
$ export SMALL
$ SMALL="NO"
$ ./luahost script.lua
-- lua_State* L = luaL_newstate(); /* initialize Lua */
-- luaL_openlibs(L); /* standard libraries */
-- lua_register(L, "average", c_average); /* private library */
-- luaL_loadfile(L, argv[1]) ...
C: argument[ 4 or  -1]= 4
C: argument[ 3 or  -2]= 3
C: argument[ 2 or  -3]= 2
C: argument[ 1 or  -4]= 1
Lua: ave    = 2.5
Lua: width  = 100
Lua: height = 200
-- script.lua load and executed.
-- lua_getglobal(L, "width"); /* push Lua variable width */
=== DUMP STACK top=[1] --> bottom=[1] ===
C: stack[ 1 or  -1] type=    number;  value= 100
-- lua_getglobal(L, "height"); /* push Lua variable height */
=== DUMP STACK top=[2] --> bottom=[1] ===
C: stack[ 2 or  -1] type=    number;  value= 200
C: stack[ 1 or  -2] type=    number;  value= 100
-- lua_getglobal(L, "ave"); /* push Lua variable ave */
=== DUMP STACK top=[3] --> bottom=[1] ===
C: stack[ 3 or  -1] type=    number;  value= 2.5
C: stack[ 2 or  -2] type=    number;  value= 200
C: stack[ 1 or  -3] type=    number;  value= 100
-- a = lua_tonumber(L, -1); /* read ave at -1 in the stack */
C: Average = 2.5
-- h = lua_tonumber(L, -2); /* read height at -2 in the stack */
C: Height  = 200
-- h = lua_tonumber(L, -3); /* read width at -3 in the stack */
C: Width   = 100
C: Volume  100 * 200 * 2.5 = 50000
$ SMALL="YES"
$ ./luahost script.lua
-- lua_State* L = luaL_newstate(); /* initialize Lua */
-- luaL_openlibs(L); /* standard libraries */
-- lua_register(L, "average", c_average); /* private library */
-- luaL_loadfile(L, argv[1]) ...
C: argument[ 4 or  -1]= 4
C: argument[ 3 or  -2]= 3
C: argument[ 2 or  -3]= 2
C: argument[ 1 or  -4]= 1
Lua: ave    = 2.5
Lua: width  = 100
Lua: height = 200
-- script.lua load and executed.
-- lua_getglobal(L, "width"); /* push Lua variable width */
=== DUMP STACK top=[1] --> bottom=[1] ===
C: stack[ 1 or  -1] type=    number;  value= 100
-- lua_getglobal(L, "height"); /* push Lua variable height */
=== DUMP STACK top=[2] --> bottom=[1] ===
C: stack[ 2 or  -1] type=    number;  value= 200
C: stack[ 1 or  -2] type=    number;  value= 100
-- lua_getglobal(L, "ave"); /* push Lua variable ave */
=== DUMP STACK top=[3] --> bottom=[1] ===
C: stack[ 3 or  -1] type=    number;  value= 2.5
C: stack[ 2 or  -2] type=    number;  value= 200
C: stack[ 1 or  -3] type=    number;  value= 100
-- a = lua_tonumber(L, -1); /* read ave at -1 in the stack */
C: Average = 2.5
-- h = lua_tonumber(L, -2); /* read height at -2 in the stack */
C: Height  = 200
-- h = lua_tonumber(L, -3); /* read width at -3 in the stack */
C: Width   = 100
C: Volume  100 * 200 * 2.5 = 50000

Lua program can call C functions and use values of C variables such as ones passed from OS environment variables via the hosting C program. The hosting C program can use the execution result of the Lua script.

Previous Post Top Next Post