https://github.com/johnae/classy
Classy is a class implementation for MoonScript (and Lua). It is mainly for MoonScript however since that allows a certain syntax which looks pretty good i.m.o. I haven't used it from Lua but I expect the syntax to become more verbose and definitely uglier.
I know this works with LuaJIT 2.x+. It should work with other Lua implementations too - but I haven't tried. The CircleCI tests run on LuaJIT. Please test and help out if you feel like it.
I haven't done any extensive performance tests but it seems to be slightly faster than MoonScript's built-in class implementation. Haven't compared to other implementations of which there are many.
Because MoonScript's built-in class implementation makes it difficult to use metamethods. It's also a bit verbose in it's compiled lua output. And, of course, because it's fun.
See spec/classy_spec.moon and spec/smoke_test_spec.moon for more examples.
define = require'classy'.define -- call the local whatever you like, I like 'define'
format = string.format
Date = define 'Date', ->
properties
age:
get: =>
now = os.date('*t')
age = now.year - @year
if now.month < @month
age = age - 1
else if now.month == @month
if now.day < @day
age = age - 1
age
set: (age) =>
today = os.date('*t')
@year = today.year - age
static
from_string: (str) =>
y, m, d = str\match('(%d+)-(%d+)-(%d+)')
y, m, d = tonumber(y), tonumber(m), tonumber(d)
new y, m, d
instance
initialize: (year, month, day) =>
@year, @month, @day = tonumber(year), tonumber(month), tonumber(day)
meta
__tostring: =>
y, m, d = @year, format('%02d', @month), format('%02d', @day)
"#{y}-#{m}-#{d}"
__add: (num_days) =>
s = num_days*24*60*60
d = os.time year: @year, month: @month, day: @day
newdate = os.date '*t', d+s
new newdate.year, newdate.month, newdate.day
__sub: (num_days) =>
s = num_days*24*60*60
d = os.time year: @year, month: @month, day: @day
newdate = os.date '*t', d-s
new newdate.year, newdate.month, newdate.day
Person = define 'Person', ->
accessors
attributes: {'firstname', 'lastname'}
properties
name: => "#{@firstname} #{@lastname}"
birthdate:
get: => tostring(@attributes.birthdate)
set: (date) => @attributes.birthdate = Date\from_string(date)
age:
get: => @attributes.birthdate.age
set: (age) => @attributes.birthdate.age = age
static
find: (name) =>
name = name\lower!
@some_people or= {
new('Bob', 'Johnsson', '1964-03-02'),
new('Mary', 'Jensen', '1983-05-10'),
new('Victoria', 'Hammadi', '1989-12-15')
}
[p for p in *@some_people when p.name\lower!\match name]
instance
initialize: (firstname, lastname, birthdate) =>
@attributes = {:firstname, :lastname, birthdate: Date\from_string(birthdate)}
-- subclass of Person
Employee = define 'Employee', ->
parent Person
static
from_person: (p, salary) =>
{:firstname, :lastname, :birthdate} = p.attributes
birthdate = tostring(birthdate)
new :firstname, :lastname, :birthdate, :salary
instance
initialize: (opts={}) =>
{:firstname, :lastname, :birthdate} = opts
super @, firstname, lastname, birthdate
@salary = opts.salary
d = Date.new 1978, 1, 5
print tostring(d) -- prints '1978-01-05'
new_year_1977 = d - 5
print tostring(new_year_1977) -- prints '1977-12-31'
p = Person.new 'John', 'Eriksson', '1978-01-05'
print p.firstname -- prints the firstname in the @attributes table
print p.age -- prints the calculated age
print p.birthdate -- print the birthdate
p.age = 37 -- changes the birthdate
print p.age -- prints 37
print p.birthdate -- prints the new birthdate
print p.name -- prints the name by concatenating firstname and lastname
bob = Person\find('john')[1] -- static method, finds Bob (his lastname is Johnsson)
print bob.name -- prints "Bob Johnsson"
print bob.age -- prints the calculated age
employee = Employee\from_person bob, 1000000
print employee.name -- prints "Bob Johnsson"
print employee.age -- prints calculated age
print employee.salary -- prints 1000000
-- some_table == some_table is true in lua, some_table == some_other_table is false in lua
print employee == employee -- prints true
cloned_employee = employee\dup!
print cloned_employee.name -- prints "Bob Johnsson"
print cloned_employee.age -- prints calculated age
print cloned_employee.salary -- prints 1000000
print cloned_employee == employee -- prints false
cloned_employee.salary = 100
print cloned_employee.salary -- prints 100
print employee.salary -- prints 1000000
Above demonstrates some of what this library can do. There is more, like missing_property for example. See spec/classy_spec.moon.
Running the tests requires busted https://github.com/Olivine-Labs/busted and luassert https://github.com/Olivine-Labs/luassert. Since luassert comes with busted, only busted needs to be installed really. It also requires that moonscript is installed.
On Ubuntu you might go about it like this:
sudo apt-get install luarocks luajit
sudo luarocks install busted
sudo luarocks install moonscript
To run the specs, run busted spec
.
I appreciate all feedback and help. If there's a problem, create an issue or pull request. Thanks!