Inline Cee

lua-users home
wiki

Here's a simple module that allows C code to be embedded inline in Lua code. The module invokes your C compiler (e.g. gcc) to turn the C code into a Lua function that can be executed in Lua.

Example:

local CC = require "inlinec"



local f = CC.compile [[

  int start(lua_State * L) {

    luaL_checkstring(L,1);

    lua_pushstring(L, "hello ");

    lua_pushvalue(L, 1);

    lua_concat(L, 2);

    return 1;

  }

]]



print(f("world"))  --> "hello world"

Implementation:


-- inlinec.lua

-- (c) D.Manura, 2008.

-- Licensed under the same terms as Lua itself (MIT license).

local M = {}



M.debug = false



local preamble = [[

#include <lua.h>

#include <lauxlib.h>

#include <stdio.h>

#include <stdlib.h>

]]



-- Count number of lines in string.

local function numlines(s)

  return # s:gsub("[^\n]", "")

end



-- Add lines to string so that any compile errors

-- properly indicate line numbers in this file.

local function adjustlines(src, level, extralines)

  local line = debug.getinfo(level+1,'l').currentline

  return ("\n"):rep(line - numlines(src) - extralines) .. src

end



-- Create temporary file containing text and extension.

local function make_temp_file(text, ext)

  local filename = os.tmpname() .. '.' .. ext

  local fh = assert(io.open(filename, 'w'))

  fh:write(text)

  fh:close()

  return filename

end



-- Create temporary header file with preamble.

-- The preamble is placed in a separate file so as not

-- to increase line numbers in compiler errors.

local pre_filename

local function make_preamble()

  if not pre_filename then

    pre_filename = make_temp_file(preamble, 'h')

  end

  return pre_filename

end



-- Execute command.

local function exec(cmd)

  if M.debug then print(cmd) end

  assert(os.execute(cmd) == 0, 'command failed')

end



-- Compile C source, returning corresponding Lua function.

-- Function must be named 'start' in C.

local function compile(src)

  local CC = 'gcc -O2'



  local pre_filename = make_preamble()

  src = ('#include %q\n'):format(pre_filename) .. src

  src = adjustlines(src, 2, 1)



  local modname = os.tmpname() .. '.so'



  local srcname = make_temp_file(src, "c")

  local cmd = CC .. " -shared -o '" .. modname .. "' '" .. srcname .. "' -llua"

  exec(cmd)



  local func = assert(package.loadlib(modname, 'start'))

  return func

end

M.compile = compile



return M

Possible improvements:

Possible applications:

Take #2

To avoid implementing all the platform-dependent code, we might instead reuse LuaRocks to do the building. LuaRocks already abstracts away the platform-dependencies. Example:

-- Example of compiling inline C code using LuaRocks to

-- do the compilation.

-- Tested on LuaRocks 2008-08-16 CVS version.

-- Warning: this was quickly put together for demonstration

-- purposes and might not be robust.

-- D.Manura, 2008-06. 



local build = require "luarocks.build.builtin"

local fs = require "luarocks.fs"

local type_check = require "luarocks.type_check"

local path = require "luarocks.path"

local deps = require "luarocks.deps"

local util = require "luarocks.util"



-- Create temporary file containing text and extension.

local function make_temp_file(text, ext)

  local filename = os.tmpname() .. '.' .. ext

  local fh = assert(io.open(filename, 'w'))

  fh:write(text)

  fh:close()

  return filename

end



-- C code to compile.

local srcfilename = make_temp_file([[

#include <lua.h>

#include <lauxlib.h>

  int hello(lua_State * L) {

    luaL_checkstring(L,1);

    lua_pushstring(L, "hello ");

    lua_pushvalue(L, 1);

    lua_concat(L, 2);

    return 1;

  }

  int luaopen_mymodule(lua_State * L) {

    lua_pushcfunction(L, hello);

    return 1;

  }

]], 'c')





-- LuaRock .rockspec definition

-- (provides build instructions)

local rs = {

  package = "MYPACKAGE",

  version = "1.0.0-1",

  source = { url = "unused" },

  build = {

    type = "module",

    modules = {

      mymodule = {

        srcfilename,

        libraries = {"lua"}

      }

    },

  }

}



-- This long function is based on fetch.lua:load_local_rockspec

-- but differs in that it takes a Lua table rather than a file name

-- as input.

-- FIX: It may be best if such a function were instead incorporated

-- into LuaRocks fetch.lua to avoid redundancy.

function create_rockspec(t, filename)

   assert(type(filename) == "string")



   local rockspec, err = t

   if not rockspec then

      return nil, "Could not load rockspec file "..filename.." ("..err..")"

   end



   local ok, err = type_check.type_check_rockspec(rockspec)

   if not ok then

      return nil, filename..": "..err

   end

   

   if rockspec.rockspec_format then

      if deps.compare_versions(

         rockspec.rockspec_format, type_check.rockspec_format)

      then

         return nil, "Rockspec format "..rockspec.rockspec_format..

                     " is not supported, please upgrade LuaRocks."

      end

   end



   util.platform_overrides(rockspec.build)

   util.platform_overrides(rockspec.dependencies)

   util.platform_overrides(rockspec.external_dependencies)

   util.platform_overrides(rockspec.source)



   local basename = fs.base_name(filename)

   rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")

   if not rockspec.name then

      return nil, "Expected filename in format 'name-version-revision.rockspec'."

   end



   local protocol, pathname = fs.split_url(rockspec.source.url)

   if protocol == "http" or protocol == "https" or

      protocol == "ftp" or protocol == "file"

   then

      rockspec.source.file = rockspec.source.file or fs.base_name(rockspec.source.url)

   end

   rockspec.source.protocol, rockspec.source.pathname = protocol, pathname



   -- Temporary compatibility

   if not rockspec.source.module then

      rockspec.source.module = rockspec.source.cvs_module

      rockspec.source.tag = rockspec.source.cvs_tag

   end



   local name_version = rockspec.package:lower() .. "-" .. rockspec.version

   if basename ~= name_version .. ".rockspec" then

      return nil, "Inconsistency between rockspec filename ("..

          basename..") and its contents ("..name_version..".rockspec)."

   end



   rockspec.local_filename = filename

   local filebase = rockspec.source.file or rockspec.source.url

   local base = fs.base_name(filebase)

   base = base:gsub("%.[^.]*$", ""):gsub("%.tar$", "")

   rockspec.source.dir = rockspec.source.dir

                      or rockspec.source.module

                      or ((filebase:match(".lua$") or filebase:match(".c$")) and ".")

                      or base



   if rockspec.dependencies then

      for i = 1, #rockspec.dependencies do

         local parsed = deps.parse_dep(rockspec.dependencies[i])

         if not parsed then

            return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."'"

         end

         rockspec.dependencies[i] = parsed

      end

   else

      rockspec.dependencies = {}

   end

   local ok, err = path.configure_paths(rockspec)

   if err then

      return nil, "Error verifying paths: "..err

   end



   return rockspec

end





-- Build module

rs = assert(create_rockspec(rs, "mypackage-1.0.0-1.rockspec"))

assert(build.run(rs))



-- Load module

local hello = require "mymodule"

print(hello("world")) --> "hello world"

--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited August 17, 2008 3:01 am GMT (diff)