Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Created June 11, 2020 02:28
Show Gist options
  • Save MCJack123/f2834bb16d7e5252690d40be06bcddc4 to your computer and use it in GitHub Desktop.
Save MCJack123/f2834bb16d7e5252690d40be06bcddc4 to your computer and use it in GitHub Desktop.
C preprocessor in Lua
local function split(str, sep)
local retval = {}
for m in str:gmatch("([^" .. sep .. "]+)") do table.insert(retval, m) end
return retval
end
local function dirname(str)
if str:match(".-/.-") then return string.gsub(str, "^(.*)/.*$", "%1")
else return "" end
end
local function createMacro(params, definition)
local n = #params
for i,v in ipairs(params) do params[i] = v:gsub("^%s*", ""):gsub("%s*$", "") end
return function(...)
local retval = definition
local args = {...}
for i = 1, n do
if args[i] == nil then error("Not enough arguments to macro (expected " .. n .. ", got " .. i-1 .. ")")
elseif type(args[i]) ~= nil then args[i] = tostring(args[i]) end
retval = retval:gsub("(.)" .. params[i] .. "%s*##%s*", "%1" .. args[i])
:gsub("^" .. params[i] .. "%s*##%s*", args[i])
:gsub("(.)##(.)", "%1%2")
:gsub("#" .. params[i] .. "([^%w_])", '"' .. args[i] .. '"%1')
:gsub("#" .. params[i] .. "$", '"' .. args[i] .. '"')
:gsub("([^%w_])" .. params[i] .. "([^%w_])", "%1" .. args[i] .. "%2")
:gsub("^" .. params[i] .. "([^%w_])", args[i] .. "%1")
:gsub("([^%w_])" .. params[i] .. "$", "%1" .. args[i])
:gsub("^" .. params[i] .. "%", args[i])
end
return retval
end
end
local function countGsub(t, str, m, r)
local ret, a = str:gsub(m, r)
t.n = t.n + a
if a > 0 then table.insert(t.m, {m, r}) end
return ret
end
local function macroExpansion(retval, defines)
local p = {n = 0, m = {}}
local start = tonumber(os.date "%s")
local loops = 0
repeat
p.n = 0
p.m = {}
for k,v in pairs(defines) do if k ~= v then
if type(v) == "function" then
local function replacer(argstr, a)
if a ~= nil then
local retval = argstr
argstr = a
a = retval
else a = "" end
local args = {"", n = 1}
local plevel, slevel, clevel, dq, sq, escape = 0, 0, 0, false, false, false
for c in argstr:match("^%((.+)%)$"):gmatch "." do
if c == '(' then plevel = plevel + 1
elseif c == ')' and plevel > 0 then plevel = plevel - 1
elseif c == '[' then slevel = slevel + 1
elseif c == ']' and slevel > 0 then slevel = slevel - 1
elseif c == '{' then clevel = clevel + 1
elseif c == '}' and clevel > 0 then clevel = clevel - 1
elseif c == '"' and not (dq and escape) then dq = not dq
elseif c == "'" and not (sq and escape) then sq = not sq end
if c == ',' and plevel == 0 and slevel == 0 and clevel == 0 and not dq and not sq then
args.n = args.n + 1
args[args.n] = ""
else args[args.n] = args[args.n] .. c end
escape = c == '\\' and (dq or sq)
end
for i = 1, args.n do args[args.n] = args[args.n]:gsub("^%s*", ""):gsub("%s*^", "") end
return a .. v(unpack(args))
end
retval = countGsub(p, countGsub(p, retval, "([^%w_])" .. k .. "%s*(%b())", replacer), "^" .. k .. "%s*(%b())", replacer)
elseif type(v) ~= "table" then
retval = countGsub(p, countGsub(p, countGsub(p, countGsub(p, retval,
"([^%w_])" .. k .. "([^%w_])", "%1" .. v .. "%2"),
"^" .. k .. "([^%w_])", v .. "%1"),
"([^%w_])" .. k .. "$", "%1" .. v),
"^" .. k .. "$", v)
end
end end
loops = loops + 1
if tonumber(os.date "%s") - start > 1 then
for i,v in ipairs(p.m) do print(v[1], v[2]) end
error("Too long without yielding: " .. loops .. ", " .. p.n .. ": " .. retval)
end
until p.n == 0
return retval
end
local function loadHeader(path, incpath, defines)
defines = defines or {}
defines.debugLocation = defines.debugLocation or {}
incpath = incpath or {}
local ifLevels = {n = 0, [0] = true}
local elseSkips = {}
local comment = false
local retval = ""
local lines = {}
--for line in io.lines(path) do table.insert(lines, line) end
local filein = io.open(path, "r")
for line in filein:read("*a"):gmatch "[^\r\n]+" do table.insert(lines, line) end
filein:close()
local lineno = 1
while lineno <= #lines do
local line = lines[lineno]
while line:match "\\%s*$" do
repeat lineno = lineno + 1 until lines[lineno]:gsub("^%s*", ""):gsub("%s*$", "") ~= ""
line = line:gsub("\\%s*$", "") .. lines[lineno]
end
if not comment or line:find "%*/" then
if comment then line = line:gsub(".-%*/", "") end
local shouldComment = line:find "/%*" ~= nil and line:find "%*/" == nil
line = line:gsub("//.*$", ""):gsub("/%*.*%*/", ""):gsub("/%*.*$", "")
if line:match "^%s*#" then
local cmd, tokens = line:match "^%s*#%s*(%S+)%s*(.*)"
tokens = tokens:gsub("%s*$", "")
if cmd == "ifdef" then
ifLevels[ifLevels.n+1] = ifLevels[ifLevels.n] and defines[tokens:match "^(%S+)"] ~= nil
elseSkips[ifLevels.n+1] = ifLevels[ifLevels.n+1] == true
ifLevels.n = ifLevels.n + 1
elseif cmd == "ifndef" then
ifLevels[ifLevels.n+1] = ifLevels[ifLevels.n] and defines[tokens:match "^(%S+)"] == nil
elseSkips[ifLevels.n+1] = ifLevels[ifLevels.n+1] == true
ifLevels.n = ifLevels.n + 1
elseif cmd == "if" then
if ifLevels[ifLevels.n] then
local str = "return " .. tokens:gsub("!=", "~="):gsub("!", " not "):gsub("&&", " and "):gsub("||", " or "):gsub("defined(%b())", function(a) return "defined('" .. a:sub(2, -2) .. "')" end):gsub("defined%s+([%w_]+)", "defined '%1'"):gsub("(%d+)U?L?L?", "%1"):gsub("(0x%x+)U?L?L?", "%1"):gsub("(0b[01]+)U?L?L?", "%1"):gsub("(0[0-7]+)U?L?L?", "%1"):gsub("([%d%.]+)f", "%1"):gsub("?([^():]+):", " and %1 or "):gsub("?([^():]*%b()[^():]*):", " and %1 or ")
local fn, err = loadstring(str)
if not fn then error(path .. "[" .. str .. "]" .. lineno .. err) end
setfenv(fn, setmetatable({defined = function(name) return defines[name] ~= nil end}, {__index = defines}))
local ok, res = pcall(fn)
--if not ok then print(path .. "[" .. str .. "]" .. lineno .. res) res = ok end
ifLevels[ifLevels.n+1] = res
elseSkips[ifLevels.n+1] = ifLevels[ifLevels.n+1] == true
else
ifLevels[ifLevels.n+1] = false
elseSkips[ifLevels.n+1] = false
end
ifLevels.n = ifLevels.n + 1
elseif cmd == "elif" then
if ifLevels[ifLevels.n-1] and not elseSkips[ifLevels.n] then
local str = "return " .. tokens:gsub("!=", "~="):gsub("!", " not "):gsub("&&", " and "):gsub("||", " or "):gsub("defined(%b())", function(a) return "defined('" .. a:sub(2, -2) .. "')" end):gsub("defined%s+([%w_]+)", "defined '%1'"):gsub("(%d+)U?L?L?", "%1"):gsub("(0x%x+)U?L?L?", "%1"):gsub("(0b[01]+)U?L?L?", "%1"):gsub("(0[0-7]+)U?L?L?", "%1"):gsub("([%d%.]+)f", "%1"):gsub("?([^():]+):", " and %1 or "):gsub("?([^():]*%b()[^():]*):", " and %1 or ")
local fn, err = loadstring(str)
if not fn then error(path .. "[" .. str .. "]" .. lineno .. err) end
setfenv(fn, setmetatable({defined = function(name) return defines[name] ~= nil end}, {__index = defines}))
local ok, res = pcall(fn)
--if not ok then print(path .. "[" .. str .. "]" .. lineno .. res) res = ok end
ifLevels[ifLevels.n] = res
elseSkips[ifLevels.n] = elseSkips[ifLevels.n] or ifLevels[ifLevels.n] == true
else
ifLevels[ifLevels.n] = false
end
elseif cmd == "else" then
if elseSkips[ifLevels.n] or not ifLevels[ifLevels.n-1] then
ifLevels[ifLevels.n] = false
elseSkips[ifLevels.n] = false
else
ifLevels[ifLevels.n] = not ifLevels[ifLevels.n]
elseSkips[ifLevels.n] = true
end
elseif cmd == "endif" and ifLevels.n > 0 then
ifLevels[ifLevels.n] = nil
ifLevels.n = ifLevels.n - 1
elseif cmd == "error" and ifLevels[ifLevels.n] then
error(path .. ": " .. lineno .. ": " .. tokens)
elseif cmd == "warning" and ifLevels[ifLevels.n] then
print(path .. ": " .. lineno .. ": Warning: " .. tokens)
elseif cmd == "include" and ifLevels[ifLevels.n] then
if tokens:find '<' then
local ppath = tokens:match("(%b<>)"):sub(2, -2)
local found = false
for _,v in ipairs(incpath) do
local file = io.open(v .. "/" .. ppath, "r")
if file then
file:close()
retval = retval .. "\n" .. loadHeader(v .. "/" .. ppath, incpath, defines)
found = true
break
end
end
if not found then error("Could not find header " .. ppath) end
elseif tokens:find '"' then
local ppath = tokens:match("(%b\"\")"):sub(2, -2)
local found = false
for _,v in ipairs(incpath) do
local file = io.open(v .. "/" .. ppath, "r")
if file then
file:close()
retval = retval .. "\n" .. loadHeader(v .. "/" .. ppath, incpath, defines)
found = true
break
end
end
if not found then
local file = io.open(dirname(path) .. "/" .. ppath, "r")
if file then
file:close()
retval = retval .. "\n" .. loadHeader(dirname(path) .. "/" .. ppath, incpath, defines)
else error("Could not find local header " .. ppath) end
end
else error("Invalid include path: " .. tokens) end
elseif cmd == "define" and ifLevels[ifLevels.n] then
local name, value
if tokens:match "^[^%s(]+%(" then name, value = tokens:match "^([^(]+%b())%S*%s*(.*)"
else name, value = tokens:match "^(%S+)%s*(.*)" end
if name ~= nil and value == nil then value = "" end
if name:find "%(" then defines[name:match "^[^(]+"] = createMacro(split(name:match("%b()"):sub(2, -2), ","), value)
else defines[name] = tonumber(value:gsub("(%d+)U?L?L?", "%1"):gsub("(0x%x+)U?L?L?", "%1"):gsub("(0b[01]+)U?L?L?", "%1"):gsub("(0[0-7]+)U?L?L?", "%1"):gsub("([%d%.]+)f", "%1"), nil) or value end
defines.debugLocation[name:find "%(" and name:match "^[^(]+" or name] = path .. ":" .. lineno
elseif cmd == "undef" and ifLevels[ifLevels.n] then
defines[tokens:match "^(%S+)"] = nil
end
elseif ifLevels[ifLevels.n] and line:gsub("^%s*", ""):gsub("%s*$", "") ~= "" then
retval = retval .. "\n" .. macroExpansion(line, defines)
end
comment = shouldComment
end
lineno = lineno + 1
end
return retval
end
return loadHeader
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment