This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
ElvishJerricco's profile picture

LuaLua - OOP Programming Language for CC. NEW - Anonymous Function Syntax

Started by ElvishJerricco, 09 September 2013 - 04:01 PM
ElvishJerricco #1
Posted 09 September 2013 - 06:01 PM
LuaLua is available on Team C^3's github here.

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
LuaLua was built for ComputerCraft. That's the only Lua environment I've dealt with, and I don't expect to deal with many others. But the load.lua file is the only one that should need modification to run properly in any Lua 5.1 environment. LuaJIT will not work because of the differing bytecode.

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.
Finally, indexing the properties as globals from within the class will work the same as indexing them from the object or from self.


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.
Edited on 02 April 2015 - 11:02 PM
Vilsol #2
Posted 09 September 2013 - 06:46 PM
Seems very interesting! the only problem is that the syntax is different from the normal standards, which means that it will be used only by enthusiasts (me) who code in other languages too.
Also, are you able to use the old syntax while using this?
ElvishJerricco #3
Posted 09 September 2013 - 07:02 PM
Seems very interesting! the only problem is that the syntax is different from the normal standards, which means that it will be used only by enthusiasts (me) who code in other languages too.
Also, are you able to use the old syntax while using this?

Yea I probably should have made it more clear that this is %100 compatible with vanilla Lua. I made sure to add no syntax that could possibly break any old Lua code under any circumstances.
rhyleymaster #4
Posted 10 September 2013 - 12:58 AM
LuaLua… can I call it LuaCeption?
ElvishJerricco #5
Posted 10 September 2013 - 01:06 AM
LuaLua… can I call it LuaCeption?

Well it is a Lua compiler written in Lua to compile to the Lua VM, all from within the Lua VM. Plus it has objects which are instances of classes which are instances of classes which are instances of a class which is an instance of itself which is a subclass of a class which is an instance of the class that it is a superclass for.
oeed #6
Posted 10 September 2013 - 05:48 AM
As an Objective-C developer, I have to say that this looks pretty cool. What was your decision behind using curly brackets instead of square ones, however?
GravityScore #7
Posted 10 September 2013 - 09:02 AM
Obj-C developer here too, really awesome!

As an Objective-C developer, I have to say that this looks pretty cool. What was your decision behind using curly brackets instead of square ones, however?

Probably to distinguish between instance methods of a class and normal functions outside of a class. This might have been able to do dynamically though (like, just infer it at runtime whether the function is of a class or not).
ElvishJerricco #8
Posted 10 September 2013 - 11:02 AM
As an Objective-C developer, I have to say that this looks pretty cool. What was your decision behind using curly brackets instead of square ones, however?

Because of Lua's ability to do table:function() with a colon like that, the syntax would have been ambiguous if I used square brackets for both types of function calls. For instance


@[name:functionCallForParameter() withOtherName:...]

The compiler would have had to choose whether you mean a function beginning with name and function call as parameter, or if you meant name is a table, self-call that function to get the object we want to call "withOtherName:" on.
ElvishJerricco #9
Posted 10 September 2013 - 04:09 PM
I might make a graphics library similar to Apple's Cocoa with this. OOP is THE way to do GUIs in my opinion and Cocoa is just really well thought out.
Vilsol #10
Posted 10 September 2013 - 04:54 PM
I might make a graphics library similar to Apple's Cocoa with this. OOP is THE way to do GUIs in my opinion and Cocoa is just really well thought out.

That is a very good idea!
oeed #11
Posted 10 September 2013 - 05:44 PM
Yea, this is great. I would maybe consider changing the name though, Lualua sounds a bit funny :P/> Maybe something like CocoaL, I don't know, just a thought.
ElvishJerricco #12
Posted 11 September 2013 - 01:31 PM
I'm considering two new ideas for LuaLua and would like some input.
  1. A modules system
  2. This would overwrite os.run so that any programs run via that function would have a require() function in their environments. Using require("Button.lua") would load Button.lua from the same directory as the file calling require() and return whatever Button.lua returns. Of course you can obviously require() absolute paths as well.
    
    -- Button.lua
    local @class Button : SuperClass
        function @(initWithWidth:w height:h x:x andY:y)
            @[super initWithWidth:w height:h x:x andY:y]
            -- do initialization
            return self
        end
    end
    
    return Button -- return the class for those requiring this file
    
    
    -- program.lua
    local Button = require("Button.lua") -- program has a Button file alongside it.
    local myButton = @[@[Button new] initWithWidth:w height:h x:x andY:y]
    @[myButton show]
    
    So the usage here is obvious. A file returns a value, and that's what programs get. And os.run will keep all modules loaded in a table so that any time a program calls require() more than once for the same file, they get the exact same value and the file isn't run again. But this only persists across an os.run call. Two separately running files will get two separate modules from the same file.
  3. A revamp of the @property directive
  4. At the moment, @property just auto-declares global setters and getters. This allows it to work outside a class setting. But I can't see any decent use case outside a class setting because you can only reference the property by name by referencing an object. In a normal setting you'd still have to specifically call @{setter:} and @{getter}, which is a bit pointless. So what I am considering doing is separating the properties from normal instance methods. This way you can iterate over all the things declared as properties in an object while still being able to override the setter and getter. Plus this allows me to do shared properties, where one object can share a property with another. Thus, if you do obj.a = 3 on the first object, it sets obj2.a as well. This cannot be done under the current system.

So yea. Those are the ideas. Let me know if there's anything wrong with them. One of my concerns is that the require() function might be overstepping the bounds of LuaLua. LuaLua is a language, not an API. But I can't think of a better place to put it than in LuaLua.



EDIT: OK so I basically decided to do the module system and kept the property system the same.
MudkipTheEpic #13
Posted 13 September 2013 - 06:09 PM
For the @property decorator, when you put it before a function, could you make it so you don't have to call the function to get the value, like in Python?
ds84182 #14
Posted 13 September 2013 - 08:35 PM
I'd like to ask how you got YLang working, for me it generates invalid bytecode.
ElvishJerricco #15
Posted 13 September 2013 - 08:41 PM
For the @property decorator, when you put it before a function, could you make it so you don't have to call the function to get the value, like in Python?

I'm not sure what you mean. I don't do python. Could you give an example?

I'd like to ask how you got YLang working, for me it generates invalid bytecode.

It depends on your setup. It could be the endianness or it could be the size_t in the header. You need to use the the orig files, and load.lua in LuaLua should make most of the setup fairly apparent. The only thing I needed to change was size_t, not so it would work in CC (the default size_t worked in CC), but to make it work in the lightweight emulator on a 64bit computer. I might be forgetting something so come back with any problems and I'll help iron them out.
MudkipTheEpic #16
Posted 14 September 2013 - 09:00 AM
Pulled off StackOverflow:



class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276
ElvishJerricco #17
Posted 14 September 2013 - 11:46 AM
Pulled off StackOverflow:



class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Although there's no special syntax for it, you can already do this. Just overwrite either the setter or the getter to customize it. The dot notation just calls setters and getters.


@class MyClass : LuaObject
    @property test = _test
    function @(getTest)
        -- extra calculation
        return _test
    end
end
MudkipTheEpic #18
Posted 14 September 2013 - 12:09 PM
Oh, ok. I just saw @property and was reminded of Python, just wondering if you could use it like that in LuaLua.
Darth_Ben17 #19
Posted 25 September 2013 - 09:43 PM
Looks good
ElvishJerricco #20
Posted 25 September 2013 - 10:09 PM
Looks good

Thanks!
tuogex #21
Posted 16 October 2013 - 08:28 PM
I'm liking this very much. Definitely good for anyone who misses Objective-C-like classes in Lua.
ElvishJerricco #22
Posted 18 October 2013 - 03:27 PM
Updated the property system. Much more powerful now but restricted to working inside classes rather than in the global space. Re-read the properties section for more detail.
Imque #23
Posted 19 October 2013 - 07:45 PM
That is really nice code and functionality.. +1
ElvishJerricco #24
Posted 20 October 2013 - 09:19 AM
That is really nice code and functionality.. +1

=) Thanks!

Anyway

I'm working on changing the syntax for methods a bit. It'll break old LuaLua programs but I think it's a little nicer.


-- instead of this
@[@[MyClass new] init]
@{globalCall}
-- it will be this
||MyClass new| init|
|@ globalCall|

Basically just replacing the weird @bracket system with vertical bars. Should work fine.
theoriginalbit #25
Posted 20 October 2013 - 09:25 AM
I'm working on changing the syntax for methods a bit. It'll break old LuaLua programs but I think it's a little nicer.


-- instead of this
@[@[MyClass new] init]
@{globalCall}
-- it will be this
||MyClass new| init|
|@ globalCall|

Basically just replacing the weird @bracket system with vertical bars. Should work fine.
Won't really be inspired by Objective-C then will it :P/>

Also, just write a parser to support old versions by converting them into the new version. Perhaps the easiest solution is to have a header, if the file header is missing, parse it.
ElvishJerricco #26
Posted 20 October 2013 - 09:43 AM
Also, just write a parser to support old versions by converting them into the new version. Perhaps the easiest solution is to have a header, if the file header is missing, parse it.

That's not a bad idea. And I still think it's Obj-C-like. Just uses a different ascii character =P

EDIT: Oh I should also note that declaring methods will be a little different.


-- instead of this
function @(methodName:param)

end
-- it will be this
function (methodName:param)

end
Basically just removing the unnecessary @ in front of the parentheses.
ElvishJerricco #27
Posted 20 October 2013 - 10:03 AM
There. Updated. Didn't include legacy support because it's not hard to adopt, and writing a legacy parser would have been harder than I expected.
Wobbo #28
Posted 07 November 2013 - 12:40 PM
This is looking really nice! It reminded me of Obj-C before I saw it was modelled after it :P/>
As a name change, may I suggest Objective-Lua? Since it is so heavily inspired on Objective-C.
ElvishJerricco #29
Posted 07 November 2013 - 01:44 PM
This is looking really nice! It reminded me of Obj-C before I saw it was modelled after it :P/>/>
As a name change, may I suggest Objective-Lua? Since it is so heavily inspired on Objective-C.

Thought about that. All in all, LuaLua isn't meant to be Object Oriented Lua. It's supposed to be an extension of it, hence a name that appears to just extend Lua.
robhol #30
Posted 09 January 2014 - 12:24 PM
How feasible would it be to fork this and instead base the syntax on something that… well, isn't ObjC?
ElvishJerricco #31
Posted 16 January 2014 - 10:35 PM
How feasible would it be to fork this and instead base the syntax on something that… well, isn't ObjC?

You don't have to use the ObjC syntax. Conventional lua syntax still works.


@class A:LuaObject
	function myInstanceMethod()
		print("Works like a charm")
	end
end

local obj = A.new().init()
obj.myInstanceMethod()

Any function that doesn't take parameters is under the same name as it would normally be so new and init work fine, and there's no other functions built in except the subclass method which has no reason to be called manually. So if you don't want to use the new syntax you don't have to (unless your working with someone else's code who does use it). And even when there are parameters you can still access the method in vanilla Lua syntax.


NewClass = SuperClass["subclassWithClassInstantiator:andObjectInstantiator:"](function(self, super)
	-- static closure; same as body of the optional @static block
end, function(self, super)
	-- instance closure; same as body of class block
end)

This example also demonstrates creating classes without the LuaLua syntax.

Anyways three fourths of the work in LuaLua is compiler work, while the other fourth is runtime work. So if you're willing to redo almost all the work I've done for the sake of using the same runtime with your own syntax, go ahead.

And finally, objective c syntax is really nice. I'm curious why you don't like it. It's very descriptive of the method parameters and closing things in brackets this way looks a lot better to me.
Edited on 16 January 2014 - 09:38 PM
ElvishJerricco #32
Posted 28 February 2014 - 07:44 PM
Is anyone actually using? I'm considering pulling it from my github and just designing a language that isn't bootstrapped to Lua.
blipman17 #33
Posted 04 May 2014 - 10:38 AM
I would like to use it, but i would need a bit more documentation about objects. I understand the idea behind them and their power but can't use them due to lack of knowledge. Even if this is a death thread it would be a shame if it went to waste.
Sir_Mr_Bman #34
Posted 25 May 2014 - 04:35 PM
Just dug this up.

As someone who knows Objective-C, this looks rather nice!

Gonna have to try it out!!
ElvishJerricco #35
Posted 04 June 2014 - 05:33 AM
I would like to use it, but i would need a bit more documentation about objects. I understand the idea behind them and their power but can't use them due to lack of knowledge. Even if this is a death thread it would be a shame if it went to waste.

I apologize for the incredibly late response. I've taken quite an absence from minecraft in general lately… Anyway what isn't clear about objects? I can try to make it more clear in the post.
ElvishJerricco #36
Posted 05 October 2014 - 07:59 AM
LuaLua is now a Grin-Get package.
Edited on 05 October 2014 - 05:59 AM
ElvishJerricco #37
Posted 07 October 2014 - 01:42 AM
New anonymous function syntax in the latest version.


somethingThatTakesAFunction(\(p1, p2, ...) print(...) end)
ElvishJerricco #38
Posted 04 November 2014 - 05:41 AM
Updated. Bug fixes
DigitalMisha #39
Posted 02 January 2015 - 12:51 PM
Are there any IDEs or just editors for LuaLua? It is very hard to write on such language without specified editor.
ElvishJerricco #40
Posted 03 January 2015 - 10:02 PM
In my experience, editors aren't really that important for writing with a language. They're really nice but it's not hard to do without one.
DigitalMisha #41
Posted 04 January 2015 - 06:01 PM
Thank you for your answer!
cdel #42
Posted 06 February 2015 - 09:24 AM
I am really looking into creating full-scale programs with this, kudos to you. +1
ElvishJerricco #43
Posted 06 February 2015 - 11:13 AM
I am really looking into creating full-scale programs with this, kudos to you. +1

=) Thanks. I've made a few. Having require() and classes is a huge help in larger scale projects. My typical format is to have class files that look like this:


-- MyClass.lua
return @class : require("MySuperClass.lua")
	function (init)
		|super init|
		return self
	end
end

Basically just returning anonymously written classes.
Edited on 06 February 2015 - 10:15 AM
ElvishJerricco #44
Posted 03 April 2015 - 01:08 AM
Updated: Migrated to new ownership at Team C^3
DigitalMisha #45
Posted 31 May 2015 - 05:46 PM
What about writing that compiler/interpreter/translator for the real LuaVM. You can compile it to Lua bytecode or to Lua code
biggest yikes #46
Posted 20 June 2015 - 05:49 PM
Neat.
If only there was a way to check if the LuaLua compiler is enabled from a normal program.

EDIT:

local r, error = loadstring('@"test" = true')
print(error == nil)
Edited on 20 June 2015 - 03:58 PM
immibis #47
Posted 22 June 2015 - 05:50 AM
Why the name LuaLua? Everyone knows you have to call it Lua=Lua+1.
Lupus590 #48
Posted 22 June 2015 - 10:41 AM
Why the name LuaLua? Everyone knows you have to call it Lua=Lua+1.

That's a bit long, whay not do what C/C++ did, Lua++
Edited on 22 June 2015 - 08:41 AM
MKlegoman357 #49
Posted 22 June 2015 - 10:59 AM
Why the name LuaLua? Everyone knows you have to call it Lua=Lua+1.

That's a bit long, whay not do what C/C++ did, Lua++

Because Lua has no '++' operator. Thus, you'd do 'Lua=Lua+1' :D/>
ElvishJerricco #50
Posted 26 June 2015 - 04:08 AM
Why the name LuaLua? Everyone knows you have to call it Lua=Lua+1.

Haha shouldn't it be Lua..Lua?
Creeper9207 #51
Posted 27 June 2015 - 05:59 PM
I'm a swift developer… *stands in corner*
ElvishJerricco #52
Posted 30 June 2015 - 12:17 PM
I'm a swift developer… *stands in corner*

I too am definitely preferring Swift. Such a great language (even if it's missing some fundamental features, like the ability to define a proper Monad or Functor protocol). But making a Swift-like language for CC would be a challenge.
Edited on 30 June 2015 - 10:19 AM
tommyroyall #53
Posted 01 July 2015 - 01:43 PM
This is amazing! I haven't seen what the CC community has been making in a while but this is very impressive. What influenced the usage of the @ symbol pertaining to OOP however?

Also, has anybody implemented Moonscript yet?
ElvishJerricco #54
Posted 02 July 2015 - 06:35 AM
What influenced the usage of the @ symbol pertaining to OOP however?

Objective-C entirely. It was just a familiar symbol to use in such places.
SquidDev #55
Posted 02 July 2015 - 09:44 PM
Also, has anybody implemented Moonscript yet?

I do want to implement Moonscript after Tua, but you can always use Vanilla Lua to compile it and it should run fine on CC's Lua.
TechMasterGeneral #56
Posted 03 July 2015 - 04:15 PM
LuaLua is available on Team C^3's github here.

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
LuaLua was built for ComputerCraft. That's the only Lua environment I've dealt with, and I don't expect to deal with many others. But the load.lua file is the only one that should need modification to run properly in any Lua 5.1 environment. LuaJIT will not work because of the differing bytecode.

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.
Finally, indexing the properties as globals from within the class will work the same as indexing them from the object or from self.


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.
ew its all objective-cish… :P/>
SquidDev #57
Posted 03 July 2015 - 06:11 PM
ew its all objective-cish… :P/>

That kinda is the point! Though I'm pretty sure even ElvishJerricco agrees the syntax is a bit odd. Little tip though: you can modify the quote, and so don't need to have the entire OP in your quote.
TechMasterGeneral #58
Posted 03 July 2015 - 08:51 PM
ew its all objective-cish… :P/>

That kinda is the point! Though I'm pretty sure even ElvishJerricco agrees the syntax is a bit odd. Little tip though: you can modify the quote, and so don't need to have the entire OP in your quote.

Yeah ik i was just lazy.. I'm not an objective c person.. its hard for me to understand
クデル #59
Posted 04 July 2015 - 08:10 AM
I really wish I had to thoroughly learn this, god damn!
Creeper9207 #60
Posted 06 July 2015 - 10:59 PM
I'm a swift developer… *stands in corner*

I too am definitely preferring Swift. Such a great language (even if it's missing some fundamental features, like the ability to define a proper Monad or Functor protocol). But making a Swift-like language for CC would be a challenge.
I would like to make something similar to skript in Lua for beginners, skript is a bukkit plug in that has code like this:

On right click with stick:
-tab-stride lightening on targeted entity
My attempt to make alternative code was endiscript and it flopped instantly
Edit: you have no idea how hard that was to do on a tablet with autocorrect
Edited on 06 July 2015 - 09:01 PM
biggest yikes #61
Posted 18 July 2015 - 06:02 PM
-snip-
ew its all objective-cish… :P/>
Did you seriously just quote the OP? ._.
Edited on 18 July 2015 - 04:02 PM