lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


For a class I'm teaching, students know C and not Lua.
I wanted to write some code in Lua without having them
worry about what Lua was, where their path was set, and
so on.  So I just wrote a short Lua script to take a 
Lua file, turn either the source code or a compiled binary
into a C program, and write the C wrapper function that
loads it into C.  So for example, if you have module
uname.lua, you can turn it into a shared library uname.so
as follows:

  lua2c uname.lua > luname.c
  gcc -I/usr/include/lua5.1 -fPIC -shared \
        -Wl,-soname,uname.so -o uname.so luname.c

Or in my case you can turn it into an ordinary .o file
and link it in with a .a file.  So I can hand my students 
a libmumble.a and they don't even know they're using Lua.
Plus I know the code is always the code that is compiled in---
there is never any worry about paths or versions.

Luiz encouraged me to post the code.  I'm embarrassed to 
have anyone else see it, since there are all sorts of gross
Unixy OS hacks in there, but I have patched it only to 
the point where it doesn't use 'require', so others
might be able to use it.  Lots of ugliness packed in
a mere 110 lines!

If somebody feels like cleaning it up and putting it on the
Lua Users' Wiki, we'll all be in your debt.  The worst thing
in there now is a call to the Unix function 'tempfile', which
is a Debianism.  The right thing would be to try mktemp if
that fails, and so on.

Norman

P.S. This script highlights the property that compiled Lua
binaries are *not* portable across platforms!  I made
compilation the default because I didn't want people snooping
around with strings(1) or mucking with the source code...


#!/usr/bin/env lua
----- lua 5.1 script to convert a lua file into a C file
-----
----- Usage: lua2c foo.lua > lfoo.c     # compiled, so not portable
-----        lua2c -s foo.lua > lfoo.c  # source, and so portable

---------------- general utility functions -------------

function io.contents(filename)
  local f, msg = io.open(filename, 'r')
  if not f then return f, msg end
  local s = assert(f:read '*a')
  f:close()
  return s
end

function os.capture(cmd, raw)
  assert(io.popen)
  local f = assert(io.popen(cmd, 'r'))
  local s = assert(f:read('*a'))
  f:close()
  if raw then return s end
  s = string.gsub(s, '^%s+', '')
  s = string.gsub(s, '%s+$', '')
  s = string.gsub(s, '[\n\r]+', ' ')
  return s
end

local quote_me = '[^%w%+%-%=%@%_%/]' 
                     -- easier to complement what doesn't need quotes
local strfind = string.find

function os.quote(s)
  if strfind(s, quote_me) or s == '' then
    return "'" .. string.gsub(s, "'", [['"'"']]) .. "'"
  else
    return s
  end
end

function os.runf(...) return os.execute(string.format(...)) end
local function printf(...) return outfile:write(string.format(...)) end

-------------------------------------------------------
local lua -- program to be loaded (source or binary)

if arg[1] == '-a' or arg[1] == '-s' then -- don't compile; 
	                                 -- use ascii source
  table.remove(arg, 1)
  assert(#arg == 1)
  lua = io.contents(arg[1]):gsub('^#!.-\n', '')
else   
  assert(#arg == 1)
  local bin = os.capture 'tempfile'
  assert(os.runf('luac -o %s %s', os.quote(bin), os.quote(arg[1])) == 0)
  lua = io.contents(bin)
  os.remove(bin)
end

local libname = arg[1]:gsub('.*/', ''):gsub('%.lua$', '')

outfile = io.stdout

------------------------

outfile:write [[
#include <lua.h>
#include <lauxlib.h>

static unsigned char program[] = {
]]

outfile:write '  '

local last = lua:len()

for i = 1, last do
  local n = string.byte(lua, i)
  printf('%3d%s', n, i < last and ', ' or '')
  if i % 10 == 0 then printf '\n  ' end
end

printf '\n};\n\n'

outfile:write((([[

int luaload_$lib(lua_State *L) {
  return luaL_loadbuffer(L, (const char*)program, sizeof(program), "@$source");
}

int luaopen_$lib(lua_State *L) {
  if (luaL_loadbuffer(L, (const char*)program, sizeof(program), "@$source"))
    return luaL_error(L, "Internal library '%s' failed to parse", "$lib");
  if (lua_pcall(L, 0, 1, 0))
    return luaL_error(L, "Internal library '%s' failed to run: %s", "$lib",
                      lua_tostring(L, -1));
  if (lua_isnil(L, -1)) {
    lua_pop(L, 1);
    lua_pushboolean(L, 1);
  }
  lua_getglobal(L, "package");   // s: lib package
  lua_getfield(L, -1, "loaded"); // s: lib package loaded
  lua_remove(L, -2);             // s: lib loaded
  lua_pushstring(L, "$lib");     // s: lib loaded libname
  lua_pushvalue(L, -3);          // s: lib loaded libname lib
  lua_settable(L, -3);           // s: lib loaded
  lua_pop(L, 1);                 // s: lib
  return 1;
}
]]):gsub('$(%a+)', { lib = libname, source = arg[1]:gsub('.*/', '') })))