Skip to content

Instantly share code, notes, and snippets.

@lennyRBLX
Last active January 21, 2020 00:23
Show Gist options
  • Save lennyRBLX/65bd1c66f91c2f9bf140b94e0be72112 to your computer and use it in GitHub Desktop.
Save lennyRBLX/65bd1c66f91c2f9bf140b94e0be72112 to your computer and use it in GitHub Desktop.
A custom Pure Lua compiler to allow for static file inclusion.
-- credits to louka | merge.lua
-- helped a lot
local pattern_regex = "C_INCLUDE%s*%(?%s*['\"]([%w_/\\%.]+)['\"]%s*%)?"
local directory = "" -- adds onto all calls to get contents of files, really easy and helpful
local wrap_includes = false -- wraps files with pcall that has custom error message
local function get_line_count(string)
local result = 1
for _ in tostring(string):gmatch("\n") do
result = result + 1
end
return result
end
local function parse_path(path)
return directory .. tostring(path)
end
local function get_include_paths(include_string)
include_string = tostring(include_string)
local result = {}
for path in include_string:gmatch(pattern_regex) do
result[#result + 1] = path
end
return result
end
local function get_include_path(include_string)
include_string = tostring(include_string)
return get_include_paths(include_string)[1]
end
local function get_line(sent_script, include_string)
sent_script = tostring(sent_script)
include_string = tostring(include_string)
local s_index, e_index = sent_script:find(include_string)
local start_index
repeat
if sent_script:sub(s_index, s_index) == "\n" or (s_index < 1) then
start_index = s_index + 1
end
s_index = s_index - 1
until start_index ~= nil
local end_index
repeat
if sent_script:sub(e_index, e_index) == "\n" or (e_index > #sent_script) then
end_index = e_index - 1
end
e_index = e_index + 1
until end_index ~= nil
return sent_script:sub(start_index, end_index), start_index, end_index
end
local function get_path_contents(sent_path)
local contents, content_io
if not sent_path:find('%w.%w') then
-- assume .lua file extension
sent_path = sent_path .. ".lua"
else
sent_path = sent_path
end
sent_path = parse_path(sent_path)
content_io = io.open(sent_path, "rb")
if content_io then
contents = content_io:read("*a")
content_io:close()
end
return contents
end
local function format_contents(contents, white_space)
local result = ""
local prev_char
for i = 1, #contents, 1 do
local char = contents:sub(i, i)
if prev_char then
if char:match(".") and prev_char:match("\n") then
result = result .. white_space:sub(1, #white_space - 1) .. char
elseif char ~= "\n" then
result = result .. char
end
end
prev_char = char
end
return result
end
local function replace_string(source, start_i, end_i, repl)
source = tostring(source)
repl = tostring(repl)
local result = ""
if tonumber(start_i) and tonumber(end_i) then
result = source:sub(1, start_i) .. repl .. source:sub(end_i, #source)
else
result = source
end
return result
end
local function in_table(includes, path)
path = tostring(path)
local result = false
for _, include in pairs(includes) do
if include.path == path then
result = include
end
end
return result
end
local function build(sent_script, includes)
local result = sent_script
local included = {}
repeat
for _, path in pairs(get_include_paths(result)) do
local include = in_table(includes, path)
local s_index, e_index = result:find(path)
s_index = (s_index - 1) - #("C_INCLUDE('")
e_index = (e_index + 1) + #("')")
if included[path] then
result = replace_string(result, s_index, e_index, "")
else
local white_space = include.white_space
local wrap_front, wrap_end = "", ""
if wrap_includes then
white_space = ("\t"):sub(1, 1) .. white_space
local format_path = path:lower():gsub(" ", "_"):gsub("%s", ""):gsub("'", ""):gsub('"', ""):gsub(string.char(91), ""):gsub(string.char(93), "")
wrap_front = "local error, msg = pcall(function()\n" .. white_space:sub(1, #white_space - 1) .. "\n\t"
wrap_end = "\nif not error then\n" .. string.format("error(%s .. ' | message: ' .. msg)\n", "'" .. format_path .. "'") .. "end\n"
end
result = replace_string(
result, s_index, e_index,
wrap_front .. format_contents(include.contents, white_space) .. wrap_end
)
included[path] = true
print("built: " .. parse_path(path))
end
end
until #get_include_paths(result) == 0
return result
end
local include_table = {}
local function compile(sent_script)
sent_script = tostring(sent_script)
local l_unpack = unpack or table.unpack -- local unpack
for _, path in pairs(get_include_paths(sent_script)) do
if not in_table(include_table, path) then
print("compiled: " .. parse_path(path))
local contents = get_path_contents(path)
local include_line = get_line(sent_script, path)
local s_index, e_index = sent_script:find(path)
s_index = (s_index - 1) - #("C_INCLUDE('")
e_index = (e_index + 1) + #("')")
local white_space = ""
if include_line:find("%s") then
white_space = include_line:sub(0, s_index)
end
include_table[#include_table + 1] = {
["path"] = path,
["contents"] = contents,
["start_index"] = s_index,
["end_index"] = e_index,
["white_space"] = white_space
}
-- recursive
compile(contents)
end
end
end
local function main(path, out_path)
if not path or not out_path then return end
local in_io = io.open(path, "rb")
if not in_io then
print("failed to get contents for: " .. path)
return
end
local out_io = io.open(out_path, "w+b")
if not out_io then
print("failed to get contents for: " .. out_path)
return
end
local sent_script = in_io:read("*a")
compile(sent_script)
out_io:write(
build(sent_script, include_table)
)
in_io:close()
out_io:close()
end
return main(...)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment