Last active
November 28, 2020 18:28
-
-
Save MCJack123/497a5658c24e02aff4e4fab743f8adc7 to your computer and use it in GitHub Desktop.
Reinterpreter/compiler for my Lua class library
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 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 |
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
-- 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!") |
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
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