Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active November 28, 2020 18:28
Show Gist options
  • Save MCJack123/497a5658c24e02aff4e4fab743f8adc7 to your computer and use it in GitHub Desktop.
Save MCJack123/497a5658c24e02aff4e4fab743f8adc7 to your computer and use it in GitHub Desktop.
Reinterpreter/compiler for my Lua class library
local classcompiler = {}
--[[
-- Example syntax:
class MyClass {
num = 0
function __init(num)
self.num = num
end
static function ClassName()
return MyClass._class
end
function MyFunc()
return self.num
end
}
class MyChild extends MyClass {
function MyFunc()
return self.num * 2
end
}
]]
local class_min_lua = [[-- class.min.lua
local class; do
local metamethods = {__add=true, __sub=true, __mul=true, __div=true, __mod=true, __pow=true, __unm=true, __concat=true, __len=true, __eq=true, __lt=true, __le=true, __newindex=true, __call=true, __metatable=true}
local function tabset(t, k, v) t[k] = v; return t end
local function _wrap_method(obj, func, ...) return setfenv(func, tabset(tabset(getfenv(func), "self", obj), "super", setmetatable({}, {__index = getmetatable(obj).__index})))(...) end
local function defineClass(name, meta, def)
local c, cmt = {__class = name}, {}
if def.static then for k,v in pairs(def.static) do if metamethods[k] then cmt[k] = v else c[k] = v end end end
def.static = nil
if meta.extends then if meta.extends[1] then cmt.__index = function(self, name) for i,v in ipairs(meta.extends) do if v[name] then return v[name] end end end else cmt.__index = meta.extends end end
local __init = def.__init
def.__init = nil
cmt.__call = function(self, ...)
local omt, supers = {}, {}
local obj = setmetatable({__class = name}, omt)
if meta.extends and not __init then if meta.extends[1] then for i,v in ipairs(meta.extends) do supers[i] = v() end omt.__index = function(self, name) for i,v in ipairs(supers) do if v[name] then return v[name] end end end else omt.__index = meta.extends(...) end end
for k,v in pairs(def) do if type(v) == "function" then obj[k] = function(...) _wrap_method(obj, v, ...) end elseif k ~= "__class" then obj[k] = v end if metamethods[k] then omt[k] = obj[k]; obj[k] = nil end end
if __init then
local env = tabset(tabset(getfenv(__init), "self", obj), "super", setmetatable({}, {__index = omt.__index}))
if meta.extends then if meta.extends[1] then omt.__index = function(self, name) if #supers < #meta.extends then for i,v in ipairs(meta.extends) do supers[i] = v() end end for i,v in ipairs(supers) do if v[name] then return v[name] end end end for i,v in ipairs(meta.extends) do env[v.__class] = function(...) supers[i] = v(...) end end else omt.__index = function(self, name) omt.__index = meta.extends(); return omt.__index[name] end env[meta.extends.__class] = function(...) omt.__index = meta.extends(...) end end end
setfenv(__init, env)(...)
if meta.extends and meta.extends[1] then omt.__index = function(self, name) for i,v in ipairs(supers) do if v[name] then return v[name] end end end end
end
return obj
end
--return tabset(_G, name, setmetatable(c, cmt))[name]
return setmetatable(c, cmt)
end
class = setmetatable({}, {__call = function(self, name) return function(tab) if tab.extends or tab.implements then return function(impl) return defineClass(name, tab, impl) end else return defineClass(name, {}, tab) end end end})
end
-- end class.min.lua
]]
local keywords = {["do"] = 1, ["then"] = 1, ["repeat"] = 1, ["function"] = 1, ["function"] = 1, ["end"] = -1}
local function replacement(extends, nl)
return function(name, contents, extra)
local str = "local " .. name .. " = class \"" .. name .. "\" "
local static = ""
local isStatic = false
local function append(s) if isStatic then static = static .. s else str = str .. s end end
local function getstr() return isStatic and static or str end
if nl then str = "\n" .. str end
if extends then
local ext = contents:gsub(" +$", "")
contents = extra
if ext:find(",") then str = str .. "{extends = {" .. ext .. "}} "
else str = str .. "{extends = " .. ext .. "} " end
end
local current, stop = contents:find("[^\n]+")
local level = 0
local quote = 0
while current do
local ss = contents:sub(current, stop)
--print(ss)
if level == 0 then
local ss_short = ss:gsub("^[ \t]+", ""):gsub(" +$", "")
if ss_short == "{" or ss_short == "}" or ss_short == "" then
append(ss .. "\n")
elseif ss_short:find("function") then
--print("Match ", ss:match("^( +)function +([a-zA-Z_][a-zA-Z0-9_]*)%("))
local offset = 0
if ss_short:find("static +function") then isStatic = true; offset = #ss:match("(static +)function"); ss = ss:gsub("static +function", "function") end
local newss = ss:gsub("^([ \t]+)function +([a-zA-Z_][a-zA-Z0-9_]*)%(", "%1%2 = function("):gsub("^([ \t]+)static +function +([a-zA-Z_][a-zA-Z0-9_]*)%(", "%1%2 = function("):gsub("(function%b()).+", "%1")
level = 1
stop = current + #ss:match("^.+function[^(]*%b()") + offset
append(newss .. (ss == ss:match("^.+function[^(]*%b()") and "\n" or " "))
else
append(ss .. ",\n")
end
else
local key = ""
for c in ss:gmatch(".") do
if quote == 0 then
if c == '"' then quote = 1
elseif c == "'" then quote = 2 end
if keywords[key] and (c == ' ' or c == '(' or c == '"' or c == "'" or c == '\n') then level = level + keywords[key]; key = ""
elseif #key == 0 and (c == 'e' or c == 'd' or c == 't' or c == 'r' or c == 'f') then key = c
elseif key == 'e' and c == 'n' then key = "en"
elseif key == 'd' and c == 'o' then key = "do"
elseif key == 't' and c == 'h' then key = "th"
elseif key == 'f' and c == 'u' then key = "fu"
elseif key == "en" and c == 'd' then key = "end"
elseif key == "th" and c == 'e' then key = "the"
elseif key == "fu" and c == 'n' then key = "fun"
elseif key == "the" and c == 'n' then key = "then"
elseif key == "fun" and c == 'c' then key = "func"
elseif key == "func" and c == 't' then key = "funct"
elseif key == "funct" and c == 'i' then key = "functi"
elseif key == "functi" and c == 'o' then key = "functio"
elseif key == "functio" and c == 'n' then key = "function"
else key = "" end
elseif quote == 1 and c == '"' then quote = 0; key = ""
elseif quote == 2 and c == "'" then quote = 0; key = "" end
end
if keywords[key] then level = level + keywords[key] end
if getstr():sub(-1) == "\n" then append((" "):rep(4*level+4)) end
append(ss .. (level == 0 and ",\n" or (contents:sub(stop+1, stop+1) == "\n" and "\n" or " ")))
if level == 0 then isStatic = false end
end
if level == 0 then current, stop = contents:find("[^\n]+", stop + 1) else current, stop = contents:find("[^ \n\t]+", stop + 1) end
end
if static ~= "" then return str:gsub("(local " .. name .. " = class \"" .. name .. "\" {)([^e])", "%1\nstatic = {\n" .. static .. "},\n%2"):gsub("(local " .. name .. " = class \"" .. name .. "\" %b{} {)([^e])", "%1\nstatic = {\n" .. static .. "},\n%2")
else return str end
end
end
function classcompiler.compile(s)
local retval = class_min_lua
retval = retval .. s:gsub("\n[ \t]*class +([A-Za-z_][A-Za-z0-9_]*) *(%b{})", replacement(false, true))
:gsub("\n[ \t]*class +([A-Za-z_][A-Za-z0-9_]*) +extends +([a-zA-Z0-9_, ]+) *(%b{})", replacement(true, true))
:gsub("^[ \t]*class +([A-Za-z_][A-Za-z0-9_]*) *(%b{})", replacement(false, false))
:gsub("^[ \t]*class +([A-Za-z_][A-Za-z0-9_]*) +extends +([a-zA-Z0-9_, ]+) *(%b{})", replacement(true, false))
return retval
end
function classcompiler.loadstring(str, name) -- Lua 5.1
return loadstring(classcompiler.compile(str), name)
end
if pcall(require, "classcompiler") and shell then -- ComputerCraft console
local args = {...}
if #args < 1 then printError("Usage: classcompiler <file.lua> [args...]\n classcompiler -c <in.lua> <out.lua>"); return false end
if args[1] == "-c" then
if #args < 3 then printError("Usage: classcompiler <file.lua> [args...]\n classcompiler -c <in.lua> <out.lua>"); return false end
local file = fs.open(shell.resolve(args[2]), "r")
if file == nil then error("Could not open input file " .. args[2]) end
local out = fs.open(shell.resolve(args[3]), "w")
out.write(classcompiler.compile(file.readAll()))
file.close()
out.close()
return true
else
local name = shell.resolve(table.remove(args, 1))
local file = fs.open(name, "r")
if file == nil then error("Could not open input file " .. args[2]) end
local contents = file.readAll()
file.close()
local fn, err = classcompiler.loadstring(contents, "@" .. name)
if fn == nil then printError(err); return false end
setfenv(fn, _ENV)
return fn(table.unpack(args))
end
end
return classcompiler
-- class.min.lua
local class; do
local metamethods = {__add=true, __sub=true, __mul=true, __div=true, __mod=true, __pow=true, __unm=true, __concat=true, __len=true, __eq=true, __lt=true, __le=true, __newindex=true, __call=true, __metatable=true}
local function tabset(t, k, v) t[k] = v; return t end
local function _wrap_method(obj, func, ...) return setfenv(func, tabset(tabset(getfenv(func), "self", obj), "super", setmetatable({}, {__index = getmetatable(obj).__index})))(...) end
local function defineClass(name, meta, def)
local c, cmt = {__class = name}, {}
if def.static then for k,v in pairs(def.static) do if metamethods[k] then cmt[k] = v else c[k] = v end end end
def.static = nil
if meta.extends then if meta.extends[1] then cmt.__index = function(self, name) for i,v in ipairs(meta.extends) do if v[name] then return v[name] end end end else cmt.__index = meta.extends end end
local __init = def.__init
def.__init = nil
cmt.__call = function(self, ...)
local omt, supers = {}, {}
local obj = setmetatable({__class = name}, omt)
if meta.extends and not __init then if meta.extends[1] then for i,v in ipairs(meta.extends) do supers[i] = v() end omt.__index = function(self, name) for i,v in ipairs(supers) do if v[name] then return v[name] end end end else omt.__index = meta.extends(...) end end
for k,v in pairs(def) do if type(v) == "function" then obj[k] = function(...) _wrap_method(obj, v, ...) end elseif k ~= "__class" then obj[k] = v end if metamethods[k] then omt[k] = obj[k]; obj[k] = nil end end
if __init then
local env = tabset(tabset(getfenv(__init), "self", obj), "super", setmetatable({}, {__index = omt.__index}))
if meta.extends then if meta.extends[1] then omt.__index = function(self, name) if #supers < #meta.extends then for i,v in ipairs(meta.extends) do supers[i] = v() end end for i,v in ipairs(supers) do if v[name] then return v[name] end end end for i,v in ipairs(meta.extends) do env[v.__class] = function(...) supers[i] = v(...) end end else omt.__index = function(self, name) omt.__index = meta.extends(); return omt.__index[name] end env[meta.extends.__class] = function(...) omt.__index = meta.extends(...) end end end
setfenv(__init, env)(...)
if meta.extends and meta.extends[1] then omt.__index = function(self, name) for i,v in ipairs(supers) do if v[name] then return v[name] end end end end
end
return obj
end
--return tabset(_G, name, setmetatable(c, cmt))[name]
return setmetatable(c, cmt)
end
class = setmetatable({}, {__call = function(self, name) return function(tab) if tab.extends or tab.implements then return function(impl) return defineClass(name, tab, impl) end else return defineClass(name, {}, tab) end end end})
end
-- end class.min.lua
local foo = class "foo" {
static = {
whoIs = function(obj)
return obj.name
end,
},
name = "person",
hello = function()
print("Hello, " .. self.name .. "!")
end,
__init = function(name)
self.name = name
end,
}
local bar = class "bar" {extends = foo} {
hello = function()
super.hello()
print("How are you?")
end,
}
local obj = foo("world")
print("Foo's name is " .. foo.whoIs(obj))
obj.hello()
local baz = bar("my friend")
baz.hello()
--------
local a = class "a" {
fn = function()
print("A")
end,
}
local b = class "b" {extends = a} {
fn = function()
print("B")
end,
}
local c = class "c" {extends = a} {
fn = function()
print("C")
end,
}
local d = class "d" {extends = {b, c}} {
}
local test = d()
test.fn()
--------
local shape = class "shape" {
__init = function(sides)
if type(sides) ~= "number" then error("bad argument #1 (expected number, got " .. type(sides) .. ")") end
self.sides = sides
end,
}
local rectangle = class "rectangle" {extends = shape} {
__init = function(width, height)
_ENV.shape(4)
if type(width) ~= "number" then error("bad argument #1 (expected number, got " .. type(width) .. ")") end
if type(height) ~= "number" then error("bad argument #2 (expected number, got " .. type(height) .. ")") end
self.width = width
self.height = height
end,
}
local equiangular = class "equiangular" {extends = shape} {
__init = function(sides)
_ENV.shape(sides)
self.angle = (180 * (sides - 2)) / sides
end,
}
local square = class "square" {extends = {rectangle, equiangular}} {
__init = function(width)
_ENV.rectangle(width, width)
_ENV.equiangular(4)
end,
}
local s = square(10)
print("Width: " .. s.width .. ", height: " .. s.height .. ", angle: " .. s.angle .. ", sides: " .. s.sides)
--------
local person = class "person" {
__init = function(name)
self.name = name
end,
say = function(text)
print(self.name .. " says, \"" .. text .. "\"")
end,
}
local alice = person("Alice")
local bob = person("Bob")
alice.say("Hi, Bob!")
bob.say("Hi, how are you?")
alice.say("Very well, and you?")
bob.say("Good!")
class foo {
static function whoIs(obj)
return obj.name
end
name = "person"
function hello()
print("Hello, " .. self.name .. "!")
end
function __init(name)
self.name = name
end
}
class bar extends foo {
function hello()
super.hello()
print("How are you?")
end
}
local obj = foo("world")
print("Foo's name is " .. foo.whoIs(obj))
obj.hello()
local baz = bar("my friend")
baz.hello()
--------
class a {
function fn()
print("A")
end
}
class b extends a {
function fn()
print("B")
end
}
class c extends a {
function fn()
print("C")
end
}
class d extends b, c {
}
local test = d()
test.fn()
--------
class shape {
function __init(sides)
if type(sides) ~= "number" then error("bad argument #1 (expected number, got " .. type(sides) .. ")") end
self.sides = sides
end
}
class rectangle extends shape {
function __init(width, height)
_ENV.shape(4)
if type(width) ~= "number" then error("bad argument #1 (expected number, got " .. type(width) .. ")") end
if type(height) ~= "number" then error("bad argument #2 (expected number, got " .. type(height) .. ")") end
self.width = width
self.height = height
end
}
class equiangular extends shape {
function __init(sides)
_ENV.shape(sides)
self.angle = (180 * (sides - 2)) / sides
end
}
class square extends rectangle, equiangular {
function __init(width)
_ENV.rectangle(width, width)
_ENV.equiangular(4)
end
}
local s = square(10)
print("Width: " .. s.width .. ", height: " .. s.height .. ", angle: " .. s.angle .. ", sides: " .. s.sides)
--------
class person {
function __init(name)
self.name = name
end
function say(text)
print(self.name .. " says, \"" .. text .. "\"")
end
}
local alice = person("Alice")
local bob = person("Bob")
alice.say("Hi, Bob!")
bob.say("Hi, how are you?")
alice.say("Very well, and you?")
bob.say("Good!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment