LuaLua
LuaLua is an extension of Lua. The compiler is based on the yueliang Lua compiler, which is written in Lua. LuaLua adds:
- A new function syntax
- A class system with runtime library for support
- A properties system for easily declaring setter/getter methods
- A module system for separating code in different files
Usage
To install LuaLua, use Grin-Get.
grin-get install Team-CC-Corp/LuaLua
Then reboot your computer. Now any loaded code will use the LuaLua compiler.Functions
LuaLua brings a new (optional) syntax to writing and calling functions.
local function (myFunction) -- Creates local function myFunction
print("Hello, World!")
end
|@ myFunction| -- Calls myFunction
This simplistic demonstration shows that your function name or call is encapsulated inside the brackets rather than followed by them. This is because the method name and parameters are mixed together. You declare a function with one of these types of names by putting parentheses around the name instead of after. Parameters go along with the name. For example.
local function (doSomething:some withThing:thing)
print(some, thing)
end
|@ doSomething:"dog" withThing:"frisbee"|
This makes it a bit more clear why you would want this. Function names are more descriptive of their parameters this way. Every single parameter is named just by knowing the name of the function. It should be noted that you can still do vararg functions and calls with this. But only in the last named parameter.
local function (thisIsA:vararg func: a,b,c, ... )
print(...)
end
|@ thisIsA:"vararg" func: 1,2,3,4,5,6,7,8,9|
So every parameter is named, and vararg is still possible. But how are the functions stored internally? That is, if this function were named globally, what would the global key be? For every parameter in the function, there is a colon in the name. The above example would have the name
thisIsA:func:
and that's the string you would have to use to reference it from the global table. In fact, LuaLua actually adds a way to name global variables with symbols like colons in their names by using another new syntax.
@"someGlobalName?can#have^anything)in*it." = 4
The compiler sees the @ followed by a string and parses it as a name. You can even do it with locals. And obviously LuaLua functions.
local function (someFunction:param secondParam:param2)
return param + param2
end
local x = @"someFunction:secondParam:"(firstParam, secondParam)
LuaLua also adds a new syntax for anonymous functions. It's much less wordy so it looks more lightweight.
somethingThatTakesAFunction(\(p1, p2, ...)
print(...)
end)
Classes
Classes in LuaLua are very Objective-C inspired. That's where the new function syntax came from! So let's jump right in.
local @class MyClass : LuaObject
function (init)
|super init|
print("MyClass initializing")
return self
end
function (myMethod:param)
print(param)
end
end
local obj = ||MyClass new| init|
|obj myMethod:"method!"|
Three things should be apparent from this.
1. Functions stored in a table can also be indexed and called using the new syntax. In a global or local call, the @ represents using the global scope as an object. Here, you actually use an object as the object.
2. Classes are objects! They're just special in that they have code to create an instance of themselves, invoked by calling the "new" method on them.
3. The self variable is equivalent to the object returned by new and init.
But some of the details might be unapparent. For one, every class MUST have a superclass. LuaObject is the only class without one. The superclass is denoted by the expression after the colon after the class name. Classes can be local or global, and even be indexes of tables (@class tData.MyClass : tData.SuperClass). Classes really are just objects you can shuffle around just like anything else. You can even declare them anonymously like functions.
local t = {}
for i = 1, 100 do
t[i] = @class : LuaObject
function (test)
end
end
end
Since classes are also objects, they can have their own instance methods (that's what the "new" method is, for example).
local @class MyClass : LuaObject @static
-- static class stuff
local numberOfInstances = 0
function (new)
numberOfInstances = numberOfInstances + 1
return |super new|
end
function (printNumInstances)
print(numberOfInstances)
end
end -- end static class
-- instance object stuff
function (instanceMethod)
print("instance!")
end
end -- end instance class
|MyClass printNumInstances| -- prints 0
local obj = |MyClass new|
|MyClass printNumInstances| -- prints 1
|obj instanceMethod| -- prints instance!
The least obvious thing about this implementation of OOP in Lua is exactly how it works. How is it that when @(test) is declared, it puts it in the method space of the object instead of the global space the class was declared in? In LuaLua, classes are implemented via closures, or functions. Here's an example which doesn't not use the syntactic sugar of @class to declare a class.
local MyClass = |LuaObject subclassWithClassInstantiator:function(self, super)
-- static class stuff
local numberOfInstances = 0
function (new)
numberOfInstances = numberOfInstances + 1
return |super new|
end
function (printNumInstances)
print(numberOfInstances)
end
end andObjectInstantiator:function(self, super)
-- instance object stuff
function (instanceMethod)
print("instance!")
end
end|
Notice that inside those closures, the static and instance class code are the exact same. When creating a class, you call the subclass method of the superclass. The first argument is a closure function for the static class. The second is for the instances. Before explaining exactly what the static class is (or rather, the metaclass), first it's important to know how these closures are instantiated.
A class has the object instantiator held inside itself. Whenever you call new, an object is created, and essentially the instantiator function has its global environment set to the object, then the function is called. Thus, any globals declared by the function are placed in the object. Of course it's much more complicated than that in reality in order to allow for some of the features of the LuaLua runtime and to allow the super parameter to those functions to work, but you get the gist.
What is this metaclass thing though? Well every object must have a class. And every class is an object. So what's the class for a class? The metaclass! The structure of classes and metaclasses in LuaLua is directly copied from Objective-C. An object of a unique type is instantiated from unique instantation details from its class. Since classes have unique details such as static methods, they have to be instantiated from some other class that is much less unique. The metaclass. All metaclasses are instances of LuaObject's metaclass, which is an instance of itself. Oh my… Worse, that base metaclass is a subclass of LuaObject, which is an instance of that metaclass. It's very complicated so you can research the Objective-C metaclass system if you want to know more. Or just read the runtime.lua file. But the point is, every single class is an instance of some class, and LuaObject is the only class without a superclass. No other exeptions to either rule.
Properties
Just like Objective-C, an owner of a LuaLua object cannot access that object's data directly. All instance data is stored in local variables for the object. Everything available from the object is methods. You must use accessor methods. Fortunately, the @property directive of LuaLua makes this painless. This directs the compiler to create a local variable, a setter, and a getter. As of the latest version, properties can only be created inside classes.
@class MyClass : LuaObject
@property myProp
function init()
|super init|
|@ setMyProp:3|
print(|@ getMyProp|) -- prints 3
return self
end
end
The name of the local variable used by the setter and getter is always an underscore followed by the property name. However, this doesn't matter because you can't even access the local variable. It's closed by the VM immediately after creating the methods, as if their declarations where in a do-end block. But if you want access to the local variable, no problem! It's easy to make that so.
@class MyClass : LuaObject
@property myProp = myLocalName
function init()
|super init|
|@ setMyProp:3|
myLocalName = 4
print(|@ getMyProp|) -- prints 4
return self
end
end
It simply sets the name to whatever name is after the = sign, and doesn't close it after declaration.
The LuaLua runtime also adds some nice features for accessing properties in objects. The @property syntax is just fancy syntax for a certain method call.
@property a
-- equivalent to
do
local _a
|@ setProperty:"a"
withGetter:function() return _a end
named:"getA"
andSetter:function(v) _a = v end
named:"setA"|
end
This method is an instance method of LuaObject which lets you create a property. What this means is that properties associate a property name with a getter, getter name, setter, and setter name. Whenever indexing an object with a key that is a known property name, the getter is found by the associated getter name, it's called, and the result is returned.
local @class A:LuaObject
@property(setter=setMe,getter=getMe) prop = _prop
function (setMe:v)
print(v)
_prop = v
end
function getMe()
print(_prop)
return _prop
end
end
local obj = ||A new| init|
obj.prop = 3 -- prints 3
print(obj.prop) -- prints 3 twice
When prop is set, the object searches its property map for the property prop, finds the associated setter, which is overwritten in this case with a custom setter, and calls it.
This also shows the attributes system for properties. Currently there are four property attributes you can set.
- setter=<name> The name to associate the property's setter with.
- getter=<name> The name to associate the property's getter with.
- readonly Makes the property read only. Attempting to write it results in error.
- writeonly Makes the property write only. Attempting to read it results in error.
local @class A:LuaObject
@property a
function init()
|super init|
a = 4
-- equivalent to
self.a = 4
return self
end
end
Modules
LuaLua also adds a nice module system. It works by overwriting os.run and inserting a "require" function in the environment. So any programs run through os.run have access to the module system.
-- MyClass.lua
local @class MyClass : LuaObject
function (test)
print("test")
end
end
return MyClass
-- program.lua
local MyClass = require("MyClass.lua")
local obj = ||MyClass new| init|
|obj test|
How it works should be fairly obvious. A file run through os.run gets a require function which finds a file by the name passed to it and runs it. Whatever the file returns is returned by the require() call. Require looks for the file in the same directory as the file calling require(). So if you have this directory structure:
folder
| program.lua
| subfolder
| | MyClass.lua
program.lua would use require("subfolder/MyClass.lua"), not require("folder/subfolder/MyClass.lua"). And of course if the path passed to require begins with a / or \ it starts at the root directory instead. And obviously whenever a module calls require(), the path is relative to the module, not the program initially run.
Requiring a module twice does not run the file twice. The value returned the first time is saved in a table and then returned every time. But that's only on a per-program basis. That is, if your program exits, any programs run afterwards (or even while yours is still running) won't get the saved result, but a new one from running the file again. This is so that modules can have per-program data instead of global data.
Have fun!
I hope LuaLua is useful to some of you. I spent more time than I'd care to admit making the compiler work. Use it only for good! Or evil. Or whatever.