Created
          June 11, 2020 02:28 
        
      - 
      
- 
        Save MCJack123/f2834bb16d7e5252690d40be06bcddc4 to your computer and use it in GitHub Desktop. 
    C preprocessor in Lua
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | 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