Zack Lee
Zack Lee

Reputation: 3044

SWIG typemap for a function that returns various types

EDITED:

I'm trying to create a simple getter function based on variable name used in Lua.

For example, it can be used like the following in Lua.

num = 123
str = "hello"
print(my.getValue("num")); --> result: 123
print(my.getValue("str")); --> result: hello

And this is myBindings.h file

static void getValue(const char *name, lua_State *L)
{
    lua_getglobal(L, name);
    switch (lua_type(L, -1))
    {
        case LUA_TBOOLEAN:
            //return boolean
            break;
        case LUA_TNUMBER:
            //return number
            break;
        case LUA_TSTRING:
            //return string
            break;
        case LUA_TTABLE:
            //return table
            break;
        default:
            //return nil
            break;
    }
    lua_pop(L, 1);
}

And this is myBindings.i file.

%module my
%{
    #include "myBindings.h"
%}

%include <stl.i>
%include <std_except.i>
%include <exception.i>
%include <typemaps.i>

%typemap(default) (lua_State *L) 
{
    $1 = L;
}

%include "myBindings.h"

How do I create a SWIG typemap so getValue function returns various types in Lua?

Upvotes: 2

Views: 317

Answers (1)

Henri Menke
Henri Menke

Reputation: 10939

There is one things wrong with the getValue function. When you pop after the switch statement, you will pop whatever you pushed inside the switch statement. I guess the intention is rather to pop the global whose type you query off the stack. Therefore I just save the type in a local variable and pop the global immediately after. For the sake of this example I'm pushing some dummy values.

static void getValue(const char *name, lua_State *L)
{
    lua_getglobal(L, name);
    switch (lua_type(L, -1))
    {
        case LUA_TBOOLEAN:
            lua_pushboolean(L, true);
            break;
        case LUA_TNUMBER:
            lua_pushnumber(L, 3.14);
            break;
        case LUA_TSTRING:
            lua_pushstring(L, "Hello world!");
            break;
        case LUA_TTABLE:
            lua_newtable(L);
            lua_pushstring(L, "value");
            lua_setfield(L, -2, "key");
            break;
        default:
            lua_pushnil(L);
            break;
    }
    // Before we can return we have to clean up the stack.  There is
    // currently the global "name" and the return value on the stack in this order
    //
    // 1. Return value
    // 2. "name"
    //
    // We cannot just lua_pop(L, 1) because that would remove the
    // return value which we of course want to keep.  To pop an
    // element at a specific position we have to use lua_remove.
    lua_remove(L, -2);
}

The interface file looks fine, but you have to notify SWIG that you pushed to the stack inside your C++ function and would like to return what you pushed. Thus you have to increment SWIG_arg in an argout typemap.

%module my
%{
#include "myBindings.h"
%}

%typemap(default) (lua_State *L) {
    $1 = L;
}

%typemap(argout) (const char *name, lua_State *L) {
    ++SWIG_arg;
}

%include "myBindings.h"

Now we can check out the test script. In the first case we are calling getValue with "num" where num has type number. Thus we expect to obtain 3.14 as this is what is pushed by the C++ function. In the second case we call with "str" where str is of type string. Thus we should obtain Hello world!.

local my = require("my")

num = 123
str = "hello"
print(my.getValue("num"))
print(my.getValue("str"))

Let's try it out!

$ swig -lua -c++ test.i
$ clang++ -Wall -Wextra -Wpedantic -I /usr/include/lua5.2/ -fPIC -shared test_wrap.cxx -o my.so -llua5.2
$ lua5.2 test.lua
3.14
Hello world!

Old answer before the question was edited

The in typemap is wrong. numinputs = 0 tells SWIG that zero inputs are needed for a function with this signature. That's not true, because name is passed as an argument. However, increasing numinputs = 1 forces you to check and convert the first argument yourself. Better let SWIG handle that and remove this argument from the typemap altogether.

%typemap(in, numinputs = 0) (void **p) {
    $1 = nullptr;
}

The argout typemap doesn't make any sense whatsoever and is not even valid C++. Even if you'd correct the invalid casts it would still be a huge type-unsafe memory leak.

Upvotes: 1

Related Questions