Skip to content

Instantly share code, notes, and snippets.

@erincandescent
Created April 28, 2013 16:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erincandescent/5477347 to your computer and use it in GitHub Desktop.
Save erincandescent/5477347 to your computer and use it in GitHub Desktop.
FirstClass implementation of the Python class system in MoonScript/Lua
-- Copyright (c) 2012, Owen Shepherd
-- Permission to use, copy, modify, and/or distribute this software for
-- any purpose with or without fee is hereby granted, provided that the
-- above copyright notice and this permission notice appear in all copies.
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
-- IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
modName = ...
getUserValue = debug.getfenv
setUserValue = debug.setfenv
rawSetMetatable = debug.setmetatable
rawGetMetatable = debug.getmetatable
newproxy = newproxy
string = string
table = table
package = package
print = print
_super = nil
-- Todo: Will these become LUDs again?
s_MRO = 'MRO'
s_Object = 'Object'
s_Name = 'Name'
s_Meta = 'Meta'
-- Todo: C help
pointerString = (v) -> '(pointerString missing)'
-- Debugging tool
_repr = (v, n=1) ->
if n > 5 then return "(elided)"
switch type v
when "string" then return "\"" .. v .. "\""
when "number" then return tostring(v)
when "table"
buf = "{ "
for k, _v in pairs v
buf ..= "[" .. _repr(k, n+1) .. "] = " .. _repr(_v, n+1) .. ", "
return buf .. "}"
return "::" .. type(v) .. "::"
export repr = _repr
prepr = (...) ->
args = {...}
print(unpack([repr(v) for v in ipairs args]))
strsplit = (str, sep=':') ->
fields = {}
pattern = string.format("([^%s]+)", sep)
string.gsub(str, pattern, (c) -> fields[#fields + 1] = c)
return fields
-- Clean module function
-- Like Lua 5.1 module, without global namespace pollution
-- Returns the module table. Make this your environment
_module = (modpath, ...) ->
_M = {}
package.loaded[modpath] = _M
-- Parse the name
modcomponents = strsplit(modpath, ".")
topmod = modcomponents[1]
modname = table.remove(modcomponents)
packagename = table.concat(modcomponents, ".")
-- Define for compatibility with module()
_M._M = _M
_M._NAME = modpath
_M._PACKAGE = packagename
-- If this is a nested package, then import parent and place ourself in
-- their table
if packagename ~= ""
package = require packagename
package[modname] = _M
-- Apply decorators to the module
privM = nil
if ... then for _, f in ipairs {...}
privM = f(_M, privM)
return privM or _M
_seeAll = (_M, privM={}) ->
privMT =
__index: (privM, k) -> _M[k] or _G[k]
__newindex: _M
return setmetatable(privM, privMT)
print "module"
_ENV = _module(modName, _seeAll)
if _VERSION == "Lua 5.1"
setfenv(1, _ENV)
export module = _module
export seeAll = _seeAll
copyTable = (t) -> {k, v for k, v in pairs t}
-- Underscore indicates only safe to use on something that is explicitly an FC
-- object
_mroOf = (c) -> rawget(getUserValue(c), s_MRO)
_nameOf = (c) -> rawget(getUserValue(c), s_Name)
_instTabOf = (c) -> rawget(getUserValue(c), s_Object)
_metaOf = (c) -> rawget(getUserValue(c), s_Meta)
mroIter = (t, prev=1) -> () ->
prev = t[prev]
return prev
print "Make MRO"
---- Generates a C3 order class list in MRO format
-- FirstClass stores MROs in a format which makes it easy to begin iterating
-- from any class. For example, the MRO
-- [A, B, C, D]
-- is stored as
-- {1: A, A: B, B: C, C: D}
--
-- This function takes a class to generate an MRO for and a list of base
-- classes, transforms their MROs to be linear, then uses the C3 rules in order
-- to generate an MRO for the class.
makeMRO = (cls, bases) ->
classList = (cls) -> [c for c in mroIter _mroOf cls]
lists = [classList(c) for _, c in ipairs bases]
print("makeMRO", repr(bases), repr(lists))
hasGoodHead = (L) ->
h = L[1]
for _, l in ipairs lists
if l ~= L and l[#l] == h then return nil
return h
nextHead = () ->
for _, l in ipairs lists
print("Does[", repr(l), "] have good head?")
h = hasGoodHead l
if h then return h
error("Impossible to calculate linearization for class \"" .. _nameOf(cls) .. "\"", 2)
purge = (l, p) ->
for i, v in ipairs l
if v == p
table.remove(l, i)
purgeAll = (p) ->
for _, l in ipairs lists do purge(l, p)
remainingClasses = () ->
for _, l in ipairs lists
if #l > 0 then return true
return false
lastClass = cls
mro = { cls }
while remainingClasses!
-- Find a good head
head = nextHead!
purgeAll head
mro[lastClass] = head
lastClass = head
return mro
-- Returns the type of an object, or a descriptive error on failure
--_typeOf = (obj) ->
-- ok, cls = pcall(_classOf, obj)
-- if ok
-- return cls
-- else
-- error "typeOf invoked on non-FirstClass object(" .. cls .. ")"
_typeOf = (obj) -> rawGetMetatable(obj).__metatable
export typeOf = _typeOf
-- Returns a metatable handler for a non-binary operator
opImp = (op) ->
__op = "__" .. op
(...) =>
print(__op)
return (_typeOf self)[__op](self, ...)
-- .. for a binary operator
binOpImp = (op) ->
__op = "__" .. op
__rop = "__r" .. op
(lhs, rhs) ->
ok, tl = pcall(_typeOf, lhs)
if ok
return tl[op](lhs, rhs)
ok, tr = pcall(_typeOf, rhs)
if ok
return tr[rop](lhs, rhs)
error("No suitable implementation found for operator " .. op)
print "Build metatable"
-- Build the metatable template
objMetaTemplate =
-- Standard
call: opImp
unm: opImp
len: opImp
newindex: opImp
tostring: opImp
-- Binary
add: binOpImp
sub: binOpImp
mul: binOpImp
div: binOpImp
mod: binOpImp
pow: binOpImp
concat: binOpImp
eq: binOpImp
le: binOpImp
objMetaTemplate = { "__" .. k, fn(k) for k, fn in pairs objMetaTemplate}
-- Index must be implemented specially else we would end up in an infinite loop
-- (Note all other operator implementations depend upon a working __index!)
--
-- We redirect invocations of the __index metamethod to the __index__ method
-- on the class. __index__ is responsible for performing the actual lookup.
--
-- Why __index__ and not __index? Because the semantics are different. The
-- intention is that classes behave in a table-like manner. __index__ delegates
-- to __index whenever it cannot find a method using the usual lookup rules -
-- making the __index method a better semantic match
--
-- TODO: Implement caching for performance
objMetaTemplate.__index = (key) =>
cls = _typeOf self
instTab = _instTabOf cls
ixMethod = rawget(instTab, '__index__')
if ixMethod then return ixMethod(self, key)
return cls.__index__(self, key)
constructObject = (cls, self) ->
assert(type cls == "userdata")
assert(type self == "userdata")
env = {}
setUserValue(self, env)
return self
newObject = () => constructObject(self, newproxy true)
-- Perform an MRO-wise search of an object
mroSearch = (key, toSearch=1) =>
assert(type(self) == "userdata")
assert(key ~= nil)
print "mroSearch of " .. _nameOf(self) .. " for " .. tostring(key)
mro = _mroOf self
while true
toSearch = mro[toSearch]
--print("MRO search of " .. className(class) .. "; at " .. className(toSearch) .. "; for " .. key)
if toSearch == nil then return nil
val = rawget(_instTabOf(toSearch), key)
if val then return val
-- Processes a descriptor that has been looked up
processDescriptor = (descriptor, val) =>
print("processDescriptor", repr(descriptor), repr(val))
get, set = nil, nil
if descriptor
get = (type(descriptor) == "userdata") and descriptor.__get
set = (type(descriptor) == "userdata") and descriptor.__set
if get and set
print "Get+Set"
return get(descriptor, self)
elseif val
print "Val"
return val
elseif get
print "Get"
return get(descriptor, self)
else
print "Desc"
return descriptor
print "Make Type"
-- Initialize Type
_Type = newproxy true
export Type = _Type
constructObject(_Type, _Type)
_TypeMeta = copyTable objMetaTemplate
_TypeMeta.__metatable = _Type
rawSetMetatable(_Type, _TypeMeta)
_TypeUV = getUserValue _Type
_TypeUV[s_MRO] = { _Type }
_TypeUV[s_Name] = "Class"
_TypeUV[s_Meta] = _TypeMeta
_TypeUV[s_Object] =
__index__: (key) =>
--print("Class.__index(instanceof " .. className(typeof(self)) .. ", " .. key ..")")
print("Type.__index__", key)
val = rawget(_instTabOf(self), key)
descriptor = mroSearch(self, key)
return processDescriptor(self, descriptor, val) or _super(_Type, self).__index(self, key)
__new: (cls, name, bases, methods) ->
self = newObject(cls)
print("Class.__new(", name, repr(bases))
--print("Class.__new", class, name, bases, methods)
rawSetMetatable(self, _metaOf cls)
uv = getUserValue self
meta = copyTable objMetaTemplate
meta.__metatable = self
rawset(uv, s_Name, name)
rawset(uv, s_Object, copyTable methods)
rawset(uv, s_MRO, makeMRO(self, bases))
rawset(uv, s_Meta, meta)
print "Class.__new)"
return self
__init: (...) => nil
__call: (...) =>
print("__call", repr({...}))
inst = mroSearch(self, "__new")(self, ...)
print("__call(init)", repr(inst))
mroSearch(self, "__init")(inst, ...)
return inst
__tostring: () =>
return "<Class " .. self.name .. ">"
print "Make Object"
_Object = _Type("Object", {}, {})
export Object = _Object
_ObjectIT = _instTabOf _Object
_ObjectIT.__new = (type) ->
self = newObject(type)
rawSetMetatable(self, _metaOf type)
rawset(getUserValue(self), s_Object, {})
return self
_ObjectIT.__init = () =>
-- Nothing
_ObjectIT.__index__ = (key) =>
print("Object.__index__", key)
val = rawget(_instTabOf(self), key)
descriptor = mroSearch(_typeOf(self), key)
return processDescriptor(self, descriptor, val) or _typeOf(self).__index(self, key)
_ObjectIT.__index = (key) => nil
_ObjectIT.__newindex = (key, value) =>
descriptor = mroSearch(_typeOf(self), key)
if descriptor
set = (type(descriptor) == "userdata") and descriptor.__set
if set
return set(descriptor, self, value)
return rawset(_instTabOf(self), key, value)
_ObjectIT.__tostring = () =>
print("__tostring!", repr(rawGetMetatable(self)))
return string.format("[%s %s]", _typeOf(self).name, pointerString(self))
_ObjectIT.__eq = rawequal
-- Patch the Type MRO
_mroOf(_Type)[_Type] = _Object
print "Super & etc"
_super = (type, self) ->
proxy = newproxy(true)
proxyMT =
__index: (_, key) -> mroSearch(_typeOf(self), key, type)
rawSetMetatable(proxy, proxyMT)
return proxy
_ENV['super'] = _super
_Property = _Type("Property", {Object}, {
__init: (get, set) =>
print("Property.__init", repr(self))
_super(Property, self).__init(self)
if get then self.__get = (obj) => return get(obj)
if set then self.__set = (obj, val) => return set(obj, val)
})
export Property = _Property
print(repr rawGetMetatable _Property)
roProperty = (get) -> return _Property(get, () -> error("Setting readonly property"))
_Type.name = roProperty(() => (_nameOf self) or ("<no s_Name on " ..(tostring _typeOf self) .. " instance>"))
_Type.mro = roProperty(() => [v for v in mroIter])
n = Type.name
print("!!", repr(n))
print("Type.name:", n)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment