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

SEE Basics

Started by sci4me, 16 March 2014 - 12:30 AM
sci4me #1
Posted 16 March 2014 - 01:30 AM

Introduction
SEE is a convenient Lua runtime whose goal is to allow programmers to more easily write readable and reusable object-oriented code. The project is open source and has been in development since August, 2013.

Note: Please DO NOT skip over any parts of this tutorial. You will likely miss something important.
Another thing, please do let me know what I can do to improve this tutorial. I don't claim to be a very great writer… but I tried. I appreciate constructive criticism!
Remember, this tutorial is not final and will be improved if necessary. Expect frequent edits (at last after its first posted).

Installing SEE:
SpoilerThere are many ways you can install SEE. I am going to explain what I believe to be the easiest way. This install guide assumes that you will be installing SEE to the "/see" directory. If you wish to install to any other directory, use the SEE installer from SEE's GitHub page (I think Yevano has a pastebin for it, I will add it here if I get it).

To install SEE, simply run the fallowing:
pastebin run ntz5CEVb
This command simply runs the installer from the GitHub page but installs to the /see directory by default instead of asking for a directory.

Using SEE:
SpoilerSEE has a bunch of programs that can be used to do various things with it, located in the "see-install-dir/bin" folder. Since we installed SEE to "/see", they are in /see/bin. Any time in this tutorial, you see "see", I am referring to the "/see/bin/see" program. To make things easier, I recommend you add "/see/bin" to your computers path. There are a few ways to do so:

LUA program:

shell.setPath(shell.path() .. ":/see/bin")
The main disadvantage to this is that you must do this on every reboot.

Startup:

shell.setPath(shell.path() .. ":/see/bin")
Saved in the "startup" file. This way, you will be able to type "see" instead of "see/bin/see".

SEE Program(s) Commands:
SpoilerThe "see" program is the main program used to interact with SEE. Here is what it can do:

see -r <mainclass> <args> -cp <classpaths> --runs a SEE program without packaging or creating a runnable: runs from source
see -p <src> <dest> --packs an archive
see -u <src> <dest> --unpacks an archive
see -help --displays help (basically the above)

The "rgen" program is used to create runnable files from packed archives:

rgen  <mainclass> <target> <classarchives> --creates a runnable program from the archive "<classarchives> (can be more than one)"

The "wgen" program is used to create a script that downloads another script from the web and runs it:

wgen <url> <target> --creates download &amp;amp;amp;amp; run script using the provided URL

The "igen" program is used to create an installer script that installs SEE if it isn't already installed:

igen <runnable> <name> <target> --creates installer from a runnable SEE program

The "installer" program is obviously just the SEE installer.

Creating a SEE program:
SpoilerSEE program structure:
SpoilerSEE programs are made up of classes in packages. Here is an example directory structure for a SEE program:

DNS
	com
		sci		
			Config.lua					
			dns
				DNS.lua	  
As you can see, I have put the packages in a root folder "DNS". This is probably not really necessary, but it makes things a LOT easier in my opinion and I highly suggest you do it too. Then, I have the "com.sci.dns" package. Of course, you can use any packages you want. The actual source files "DNS.lua" and "Config.lua" have the ".lua" extension. This is necessary. If you know Java, packages work much the same way. You can put them anywhere and organize them however you want.

Each demo will demonstrate a few concepts and explain some things, each getting more complicated.

Demo 1:
SpoilerFirst, I will create my directory structure:
Spoiler

RedstoneController
	com
		sci			  
			rsc
				RSC.lua  
"RSC.lua" is my main class.

Now, creating a class… a SEE class fallows Lua syntax, so it is fairly easy to learn. There are a few "differences" but they are easy to understand. I will show a demo class and explain it:
Spoiler

function Demo.main()
	System.print("Hello, CC Forum!")
end
This is pretty much the simplest Hello World program you can create.
"System" is actually a class in SEE (located in "see.base").
All classes in "see.base" are automatically imported.
"print" is a "static" method (function, i prefer calling the methods.. same thing) in the "System" class. When I say "static", I mean that it is not an instance method; you can call it on the class instead of having to create an instance of the class to call it on.
"Demo" is the name of the class (the ".lua" file's name). "main" is the main method to all SEE programs.

If you want to pass arguments into a SEE program, add a parameter "args" to the "main" method. If arguments are passed to the program, the parameter will be an array "see.base.Array" containing the arguments. For example:


function Demo.main(args)
	for i=1, args:length() do
		System.print("Argument " .. i .. " = " .. args:get(i))
	end
end

Now, to create our RSC.lua:

--@import see.concurrent.Thread
--@import see.io.Redstone

function RSC.main()
	System.print("Starting RSC...")

	while true do
		Thread.sleep(1)
		Redstone.setOutput("left", true)
		Thread.sleep(1)
		Redstone.setOutput("left", false)
	end
end
"–@" is the prefix for a SEE "annotation". There are a few annotations that are available ("import", "extends", "native"), and I will explain them all, but for now we will just use "import".

Imports:
SpoilerTo begin importing a class, you use "–@import", fallowed by a space and the fully qualified class. For example:

--@import see.io.Redstone
--@import see.concurrent.Thread
--@import see.util.Color

You must import a class if you wish to extend it.

classes available in SEE

Demo 2:
SpoilerNow we will learn about instances of classes. Just like in Java (and any other OO language) you can create an instance of a class. I will demonstrate this by creating a program that writes data to a file.

First, I will create my directory structure:
Spoiler

DataWriter
	com
		sci			  
			dw
				DW.lua
"DW.lua" is my main class.

Again, I will show the code and explain:

--@import see.io.Path
--@import see.io.FileOutputStream
--@import see.io.DataOutputStream
--@import see.util.Math

function DW.main()
	local out = DataOutputStream:new(FileOutputStream:new(Path:new("test.dat")))

	for i=1, 1000 do
		out:writeString(i .. " = " .. Math.random(1, 1000) .. "\n")
	end

	out:flush() --should actually happen when close is called but.. to be sure, i always recommend flushing. just in case.
	out:close()
end
The main concept demonstrated here is using ":" instead of "." which is demonstrating instances of classes and instance methods.

To create an instance of a class, you use ":new" on the class name. The reason you use ":" instead of "." is (basically) because the class "DataOutputStream" is actually another object… which extends "see.rt.Class", but that is a more advanced topic which is not extremely important now. For now just know that to instantiate a class, you use ":new" and pass in any arguments the class requires.

Next, to call an instance method you simply use ":" instead of "." (in fact, "new" is actually an instance method, if you want to get technical about it). Other than that it works like normal.

So, when we say "local out = DataOutputStream:new(FileOutputStream:new(Path:new("test.dat")))", we are setting out to an instance of the "DataOutputStream" class. This instance contains all the methods defined in the "DataOutputStream" class. The "DataOutputStream" class takes an argument "OutputStream". In this case, we are giving it a "FileOutputStream". The "FileOutputStream" class requires a "Path" object (the path to the file). The "Path" class just takes a string which is the path to the file.

We are generating random numbers using the "Math" class. The reason we use "Math.random" instead of "Math:random" is because "random" is not an instance method. If you understand that fully, you are doing good so far.

Demo 3:
SpoilerNext we will talk about events.

Events in SEE work similarly to normal CC. The main difference is that events have a class associated to them now. I will show a demo of a "key" event.

First, I will create my directory structure:
Spoiler

EventDemo
	com
		sci			  
			evt
				EVT.lua
"EVT.lua" is my main class.

Show code, explain. Rinse and repeat.

--@import see.event.impl.KeyPressEvent

function EVT.main()
	System.print("Watiing for key event...")

	local evt = Events.pull(KeyPressEvent)

	System.print("Key event received!")
end

The "Events" class is automatically imported.
Fairly easy to understand I think. You can also pass in multiple arguments to "pull" to get the first event that matches any of the filters.

Now a demo of custom events. We will create another class "CustomEvent.lua" in the "com.sci.evt" package.
Note: this will use the "extends" annotation and the "super" method. I will explain these in much more detail later in the tutorial.

--@import see.event.Event

--@extends see.event.Event

function CustomEvent:init()
	self:super(Event).init("custom")  --calls the super method "init" for the Event class. will be explained in a later tutorial
end

Now we will edit our main class:

--@import com.sci.evt.CustomEvent

function EVT.main()  
	Events.register("custom", CustomEvent)

	Events.queueEvent(CustomEvent:new())
	local evt = Events.pull(CustomEvent)
	System.print(evt)
end

Note: you MUST pass in AT LEAST one string to "pull".

Demo 4:
SpoilerNext, we will build on the concepts from before and demo the "Thread" class.

First, I will create my directory structure:
Spoiler

ThreadDemo
	com
		sci			  
			td
				TD.lua
"TD.lua" is my main class.

As usual, show code and explain:

--@import see.concurrent.Thread
--@import see.util.Math

local function makeThread(label, delay)
	return Thread:new(function()
		System.print(label .. " sleeping for " .. delay .. " : " .. System.clock())
		Thread.sleep(delay)
		System.print(label .. " finished at " .. System.clock())
	end)
end

function TD.main()
	makeThread("A", Math.random(1, 5)):start()
	makeThread("B", Math.random(1, 5)):start()
	makeThread("C", Math.random(1, 5)):start()
end
Technically, the only new things demonstrated here are uses of "Thread" and Lua stuff.
We have a function that creates a thread which prints stuff, sleeps, and prints again.
The "Thread" class takes a function to run in its constructor which we are creating in the "makeThread" function. I think the functionality of the function is fairly easy to understand.
Then, in our main method, we call the "makeThread" function three times passing "A", "B", and "C" as well as a random number (1-5). The returned value is a thread, which we are calling "start" on.

Basically this starts three threads with random delays that print stuff. This program is an easy way to demo how threads actually work. Here is an example output:

A sleeping for 3 : 1.85
B sleeping for 1 : 1.85
C sleeping for 4 : 1.85
B finished at 2.85
A finished at 4.9
C finished at 5.9

As you can see, they finish in the order you would expect based on the delay: the shortest delay finishes first, the second shortest second, and the longest delay last.

If you understand this you are doing well. Next we will talk about creating instances of our own classes.

Demo 5:
SpoilerNow we will talk about creating our own class. I am going to create a "morse code" demo with Redstone.

First, I will create my directory structure:
Spoiler

MorseCode
	com
		sci			  
			morse
				MorseCode.lua
				MCC.lua
"MorseCode.lua" is my main class.

Next, as usual, I will show the code and explain.

MCC.lua:

--@import see.io.Redstone
--@import see.concurrent.Thread

function MCC:init(side)
	self.side = side
end

function MCC:dot()
	Redstone.setOutput(self.side, true)
	Thread.sleep(0.1)
	Redstone.setOutput(self.side, false)
	Thread.sleep(0.1)
end

function MCC:dash()
	Redstone.setOutput(self.side, true)
	Thread.sleep(0.3)
	Redstone.setOutput(self.side, false)
	Thread.sleep(0.3)
end

MorseCode.lua:

--@import see.concurrent.Thread
--@import com.sci.morse.MCC

function MorseCode.main()
	local mcc = MCC:new("left")

	while true do
		mcc:dot()
		Thread.sleep(1)
		mcc:dash()
		Thread.sleep(1)
	end
end

As you can see, we are creating an instance of the "MCC" class and calling the "dot" and "dash" methods on it. Fairly easy to understand.

As for the "MCC" class, we are now using a constructor. The constructor is basically the method called when the class is instantiated. It is also where class arguments are passed into. We use ":" for constructors not ".".
We also have the "dot" and "dash" methods inside the "MCC" class. Again, they use ":" instead of "." because they are instance methods.
Also, in the "MCC" constructor we are using the "self" variable which is basically the "this" of Java. It is where you can create instance variables. In this case, the instance variable I am creating is "side" which is set in the constructor. Then, to index it you just do "self." and the variable eg. "self.side".

Also, if you have two classes in the same package and you want to use one of them in the other, you do need to import it even though it is in the same package.

Take some time to make sure you really understand what we have covered so far. If you understand everything so far, you pretty much know the things that are most commonly used in SEE.
Next we will talk about extending classes and "super".

Demo 6:
SpoilerNow we will talk about the "extends" annotation and the "super" function.

First, I will create my directory structure:
Spoiler

Animals
	com
		sci			  
			animals  
				Demo.lua
				Animal.lua
				Cat.lua
				Dog.lua

Now, I will show the code and explain it as well as I can.

Animal.lua:

function Animal:init(name)
	self.name = name
end

function Animal:toString() --override the toString method
	return "Animal[" .. self.name .. "]"
end

Cat.lua:

--@import com.sci.animals.Animal

--@extends com.sci.animals.Animal

function Cat:init(name)
	self:super(Animal).init(name) --call the super constructor (from Animal) and pass in the arguments. notice the "." instead of the ":"
end

function Cat:meow()
	System.print(self.name .. ": Meow!")
end

function Cat:toString() --override the toString method and print "Cat" instead of "Animal"
	return "Cat[" .. self.name .. "]"
end

Dog.lua:

--@import com.sci.animals.Animal

--@extends com.sci.animals.Animal

function Dog:init(name)
	self:super(Animal).init(name) --call the super constructor (from Animal) and pass in the arguments. notice the "." instead of the ":"
end

function Dog:woof()
	System.print(self.name .. ": Woof!")
end

function Dog:toString()
	return "Dog[" .. self.name .. "]"
end

Demo.lua:

--@import com.sci.animals.Animal
--@import com.sci.animals.Dog
--@import com.sci.animals.Cat

function Demo.main()
	local animal = Animal:new("Alien01")
	local scruffy = Dog:new("Scruffy")
	local rex = Dog:new("Rex")
	local rocket = Cat:new("Rocket")
	local rumble = Cat:new("Rumble")

	System.print(animal)
	System.print(scruffy)
	System.print(rex)
	System.print(rocket)
	System.print(rumble)

	rex:woof()
	rocket:meow()
	scruffy:woof()
	rumble:meow()

	--cant call meow on a Dog or woof on a Cat
end

The "super" method is a method in all objects. It takes one argument: a class. This class is basically the class that contains the super method you want to call. For example:

I have three classes:
Animal.lua
Dog.lua
Dalmatian.lua

"Animal" takes a name, "Dog" takes a name, and "Dalmatian" takes nothing.

I could call "self:super(Animal).init(name)" in the "Dalmatian" constructor and it would call the constructor in "Animal" but NOT "Dog".
If I call "self:super(Dog).init(name)", it would call the "Dog" constructor. It may or may not call the "Animal" constructor. If the "Dog" constructor calls the "Animal" constructor, the "Dalmatian" constructor would call the "Animal" constructor. The same thing applies to methods.

The "extends" annotation is essentially just what allows us to do this. If "Dog" does not have "–@extends com.sci.animals.Animal", it will not extend "Animal" and will not have access to "Animal"'s methods, constructor, or fields.

Do not be alarmed if you don't understand this at first. It is a complicated thing and I find it extremely difficult to explain. If you look at the code, try it out, try making your own demos, and re-read this, you will probably be able to get a better understanding of it.

Running the demo programs:
SpoilerTo run any program, simply run the fallowing:

see -r <mainclass> <args> -cp <classpaths>
--example:
see -r com.sci.rsc.RSC -cp RedstoneController

classPath = the root folder that contains packages and classes
mainClass = the main class INCLUDING PACKAGES
args = any arguments to be passed into the main method

I intend to write more, but .. I have been writing for a few hours now. I need a break. :)/>/&amp;gt;; I hope this helps you out! Remember, let me know what I can do to improve! If you find an error, PLEASE let me know! I want to fix it ASAP! Thanks!
Edited on 09 April 2014 - 11:57 PM
Engineer #2
Posted 16 March 2014 - 04:05 PM
Shouldn't this belong in the topic of SEE? Would be a lot more convenient :P/>
sci4me #3
Posted 16 March 2014 - 04:06 PM
Shouldn't this belong in the topic of SEE? Would be a lot more convenient :P/>/>

Well I didn't create the topic.. I just work on SEE. Besides, tutorials go in tutorials? Also, it is technically in the SEE topic. :D/>
sci4me #4
Posted 20 March 2014 - 10:36 PM
I have updated the tutorial for the latest commit. I think I got all the changes… let me know if I didnt. Thanks!
LayZee #5
Posted 09 April 2014 - 06:53 PM
Please provide an example for running one of the demo programs. In particular, what should the classpath argument be?
Yevano #6
Posted 09 April 2014 - 11:00 PM
Please provide an example for running one of the demo programs. In particular, what should the classpath argument be?

For Demo 1:

see -r com.sci.rsc.RSC -cp RedstoneController
Edited on 09 April 2014 - 09:01 PM
LayZee #7
Posted 13 April 2014 - 09:31 PM
Thanks, Yevano :-) Now I get it