Inline Cee |
|
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:
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"