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"