This is the first incarnation of my attempt at creating an object-oriented library for lua and is no longer in development.
The new version is called LuaScript and escentially mantains all the functionality of Javaesque but with a twist. You can find it https://github.com/jotapapel/luascript.
require 'javaesque'
enum 'Colors' {
'RED', 'GREEN', 'BLUE'
}
interface 'Coloreable' {
color = Colors.RED,
set_color = function(self, color)
self.color = color
end
}
interface 'Drawable' : extends 'Coloreable' {
draw = function(self)
print(self.width, self.height, self.color)
end
}
class 'Shape' : implements 'Drawable' {
constructor = function(self, width, height)
self.width, self.height = width or self.width, height or self.height
end;
width = 10, height = 10
}
local shape1 = Shape(30, 30)
shape1:draw() -- 30 30 RED
shape1:set_color(Colors.BLUE)
shape1:draw() -- 30 30 BLUE
Javaesque has three types of metatypes: Enumerations, Interfaces and Classes.
It also has three functions: switch, import and catch_error.
And one keyword: static.
There are two ways to create a new metatype, but both methods follow the next structure.
- The metatype function:
enum
,interface
orclass
. - The name: A string containing the new metatypes name.
- The modifiers: Special functions that modify the metatype (each metatype has different modifiers: enumerations have no modifiers, interfaces have two and classes have three). Modifiers that accept arguments are called before those who don't.
- The prototype table: A table contaning the prototype of the metatype.
When using this method we not only create a new metatype, but we also declare a global variable that contains it.
-- all parenthesis are optional
[enum|interface|class]('Name')[:modifier_with_arguments('modifier-var1[, modifier-var2]')|:modifier_without_argument]({
[...]
})
The second method is what I call a weak definition.
When using this method instead of automatically asigning a global variable to a new metatype, we create a weak reference to it that can be then manually paired with a local variable.
Note that with this method we can't pair weak metatypes to global variables, for that please refer to the first method.
-- correct use
local key = [enum|interface|class] (nil) [:modifier 'modifier-var'|:modifier] {
[...]
}
-- forbidden
key = [enum|interface|class] (nil) [:modifier'modifier-var'|:modifier] {
[...]
}
The only way to assign new variables to all three metatypes is by declaring them in the prototype metatable, once you have declared a variable inside that table you can later change it's value (except for Enums).
Enumerations are a set of defined constants.
They don't have any modifiers.
To use them you simply call the name of the enum followed by the constant variable name Enum.CONSTANT
.
enum 'Colors' {
'RED', 'GREEN', 'BLUE'
}
print(Colors) -- enum: 0x...
Interfaces are tables used as a bueprint of variables.
interface 'Coloreable' {
color = 'red',
set_color = function(self, color)
self.color = color
end
}
print(Coloreable) -- interface: 0x....
They have two modifiers:
- extends: This modifier allows you to make one interface inherit the variables of another. The only argument this modifer accepts is the name (or names separated by a comma) of the interface you want to extend as a string (this string value can point to a global or local variable containing an interface).
interface 'Coloreable' {
color = 'red',
set_color = function(self, color)
self.color = color
end
}
interface 'Drawable' : extends 'Coloreable' {
draw = function(self)
print(self.width, self.height, self.color)
end
}
print(Drawable.draw) -- function: 0x...
print(Drawable.color) -- red
- static: This modifier makes the interface and it's variables static, which means that when implemented by a class they will all be treated as class variables (more on this in the Class tab.)
interface 'Counter' : static {
instance_number = 0,
get_count = function(self)
return self.instance_number
end,
instance_add = function(self)
self.instance_number = self.instance_number + 1
end
}
Classes are object constructors that contain a set of variables that will be passed on to objects.
class 'Shape' {
constructor = function(self, width, height)
self.width, self.height = width or self.width, height or self.height
end;
width = 10, height = 10
}
print(Shape) -- class: 0x...
To create a new object from a class you simply call the class name as a function name, followed by the arguments you determined in the constructor
function.
Unlike all metatypes you can assign variables to objects as with any other lua table.
class 'Vehicle' {}
local v1 = Vehicle()
v1.color = 'red'
print(v1) -- object: 0x...
print(v1.id) -- 0x...
print(v1.color) -- red
Classes have three modifiers:
- extends: This modifier allows you to make one class inherit the variables (interfaces and
constructor
function) of another class. The only argument this modifer accepts is the name of the class you want to extend as a string (this string value can point to a global or local variable containing a class).
class 'Animal' {
eats_food = true
}
class 'Fish' : extends 'Animal' {
moves_in_shoals = true,
number_of_fins = 3
}
local fish1 = Fish()
print(fish1.eats_food) -- true
print(fish1.number_of_fins) -- 3
- implements: This modifier allows you to implement interfaces aka assign the variables of an interface to a class. The only argument this modifier accepts is the name (or names separated by a comma) of the interfaces you want to implement (the names of the interfaces inside the string can point to global or local variables contaning an interface).
interface 'Drawable' {
draw = function(self)
print(self.width, self.height, self.color)
end
}
class 'Shape' : implements 'Drawable' {
constructor = function(self, width, height, color)
self.width, self.height = width or self.width, height or self.height
self.color = color
end;
width = 10, height = 10
}
local shape1 = Shape(55, 55, 'red')
shape1:draw() -- 55 55 red
- final: This modifier makes the class final, which means that it can't be extended by other classes. This modifier accepts no argument, and thus has to be called last.
class 'Bear' : final {
eats_fish = true
}
class 'Horse' : extends 'Bear' {}
-- Error: Cannot extend class 'Bear', class is final.
The prototype table of a class has three kind of variables:
- constructor: In the prototype table of the class declaration you can add a variable named
constructor
, a special function that is used to initialize objects. Theconstructor
function arguments must always start with a ´self´ variable.
Unlike other metatypes you can assign new variables to class objects inside this function.
class 'Vehicle' {
constructor = function(self, petrol_capacity)
self.capacity = petrol_capacity
end;
}
Vehicle.type_of_engine = 'gas_engine' -- intent to declare a variable outside the prototype metatable
local vehicle1 = Vehicle(100)
print(vehicle1.capacity) -- 100
print(Vehicle.type_of_engine) -- nil
If you are inside the constructor of a sub class (one that has extended another) you can call the function super()
inside the new constructor function to call the constructor function of the super class (the one you want to extend from) with the arguments needed.
class 'Animal' {
contructor = function(self, name)
self.name = name
end;
}
class 'Fish' : extends 'Animal' {
constructor = function(self, name, color)
super(name)
self.color = color
end;
moves_in_shoals = true,
number_of_fins = 3
}
local fish1 = Fish('tom', 'yellow')
print(fish1.name) -- tom
print(fish1.color) -- yellow
- object variable : These are the variables that will be passed on to class objects. They are defined in the prototype table just like any other variable on a regular table
key = value
.
class 'House' {
walls = 4,
construction_material = 'concrete'
}
- static or class variables: These variables will be passed on to the class itself. They are defined in the prototype table using brackets and a special static keyword
[static.key] = value
.
class 'Animal' {
[static.specimens] = 0,
[static.add_specimen] = function(self)
self.specimens = self.specimens + 1
end
}