--[[ Lua is a very simple language The keywords: and break do else elseif end false for function if in local nil not or repeat return then true until while ]] --local variable declaration local a; --variable assignment a = 2; --global variable declaration b = 3; --printing print(a+b); --boolean operators print(true and false) print(true or false) print(not true) --undeclared variables print(c) --truthiness and falsyness print(c and 3) print(nil or 3) print(false and 1 or 2) print(not nil) print(print and 2) --control structures --if if a < b then print("The world makes sense") end if a > b then print("The world is insane") else print("The world is sane once more") end if a == 0 then print("A") elseif a == 1 then print("B") elseif a == 2 then print("C") elseif b > 4 then print("D") else print("E") end --for for i=1, 10 do print(i) end for i=10, 0, -1 do print(i) end --while x = 10 while x > 3 do x = x - 1 print(x) end --repeat until x = 10 repeat x = x - 1 print(x) until x <= 3 --function add = function(a, b) return a + b end print(add(2,3)) --This is just syntactic sugar for the previous line function add(a, b) return a + b end print(add(2,3)) --functions can take any number of arguments function myprint(...) return print(...) end myprint(1,2,3) function printtwo(a,b) return print(a,b) end printtwo(1,2,3) printtwo(1,2) printtwo(1) --functions are first class objects function apply(f,x) return f(x) end --currying function add(a) return function(b) return a + b end end print(add(3)(4)) print(apply(add(3), 4)) --tables --constructed with curly braces --compared by reference print({} == {}) --as arrays array = {1, 2, 3} print(array) --array-like indexing, what do arrays start from? print(array[0],array[1], array[2], array[3]) --unpacking an array-like table print(unpack(array)) printtwo(unpack(array)) --as dictionaries t = { a = 1; b = 2; c = 3; } print(t); --dictionary-like indexing print(t["a"], t["b"], t["c"], t["d"]) --dot indexing print(t.a, t.b, t.c, t.d) --mixed key tables: mixed = {1, x = 3, y = 4, 2} print(mixed[1], mixed[2], mixed.x, mixed.y) --mixed value tables mixed2 = {"a", "b", 2, 3} print(mixed2[1], mixed2[2], mixed2[3], mixed2[4]) --combining mixed keys and values mixed3 = {1, x="a", y=2, "b"} --iterating over a table for key, value in pairs(mixed3) do print(type(key), key, type(value), value) end --tables of tables nested = { X = {1,2,3}, Y = {"a", "b", "c"} } for subtablename, subtable in pairs(nested) do print(subtablename) for _, value in pairs(subtable) do print(value) end end --functions of tables function initialize(a, b, c) return { sum = a + b + c, product = a * b * c } end instance = initialize(2,4,6) print(instance.sum, instance.product) --tables of functions operations = { sum = function(a, b, c) return a + b + c end, product = function(a, b, c) return a * b * c end } for opname, op in pairs(operations) do print(opname, op(2,4,6)) end --functions of tables of functions function CurriedOperators(a) return { sum = function(b,c) return a + b + c end, product = function(b,c) return a * b * c end } end curried2 = CurriedOperators(2) print(curried2.sum(4, 6)) print(curried2.product(4,6)) --operator overloading on tables --metatables are dictionaries of functions that have magic names. addMetatable = { __add=function(left, right) return left.value + right.value end } --you can tag normal tables with metatables in order to overload operations on the normal table A = {value = 1} B = {value = 2} setmetatable(A, addMetatable) print(A+B) --only one overloaded table is necessary, however the one on the left takes precident print(B+A) --All operators may be overloaded. Including the indexing operator: [] defaultValues = {a=1,b=2,c=3} lookAtDefaultMetatable = { __index = function(t, key) return defaultValues[key] end } T = {d=4} setmetatable(T, lookAtDefaultMetatable) print(T.a, T.b, T.c, T.d) --Syntactic sugar for a simple index overload metatable lookAtDefaultMetatable = {__index = defaultValues} setmetatable(T, lookAtDefaultMetatable) print(T.a, T.b, T.c, T.d) --index only gets called if it can't find it in the original table though T.a = 5 print(T.a, T.b, T.c, T.d) print(defaultValues.a, defaultValues.b, defaultValues.c) --This is enough to create "classes" Point2D = { --Note: the lack of a "self" parameter means this is a static method x = 0, y = 0, --constructor new = function(x, y) local newinstance = {x = x, y = y} setmetatable(newinstance, Point2D) return newinstance end, --instance operator overloads __index = function(self, key) return Point2D[key] end, __add = function(left, right) return Point2D.new(left.x + right.x, left.y + right.y) end, __sub = function(left, right) -- vector subtraction return Point2D.new(left.x - right.x, left.y - right.y) end, __mul = function(left, right) --scalar multiplication if type(left) == "number" then left, right = right ,left end return Point2D.new(left.x * right, left.y * right) end, __div = function(left, right) --scalar division assert(type(right) == "number") return Point2D.new(left.x / right, left.y / right) end, __unm = function(self) --unary minus return Point2D.new(-self.x, -self.y) end, __tostring = function(self) return "<"..self.x..", "..self.y..">" end, --instance methods dot = function(self, other) return self.x * other.x + self.y * other.y end, magnitude = function(self) return math.sqrt(self.x^2 + self.y^2) end, unit = function(self) return self/self:magnitude() end, angle = function(self, other) return math.acos(self:dot(other)/(self:magnitude() * other:magnitude())) end, rotate = function(self, angle) return Point2D.new( self.x * math.cos(angle) - self.y * math.sin(angle), self.y * math.cos(angle) + self.x * math.sin(angle) ) end } --Call the static method "new" on Point2D zero = Point2D.new() right = Point2D.new(1,0) up = Point2D.new(0,1) point = Point2D.new(7,-4) --Using overloaded operators print(right) print(up + right) print(up * 5) print(5 * up) print(up / 2) --The colon operator: syntactic sugar which passes the table as the first argument to the function at the index's value --a.b(c,...) == a:b(a, c) print(point:magnitude()) --Is the same as print(point.magnitude(point)) --Is the same as (because of __index) print(Point2D.magnitude(point)) --Call instance methods print(up:angle(right), 3*up:rotate(math.pi/3), point:magnitude()) --Syntactic sugar for classes, a.k.a. "the right way" --define a class car Car = {} --static method function Car.new(name) return setmetatable({name = name}, {__index = Car}) end --add a method called car.drive to the class Car.drive = function(self, speed) print(self.name .. " is driving at speed "..speed) end --is equivalent to function Car.drive(self, speed) print(self.name .. " is driving at speed "..speed) end --is equivalent to function Car:drive(speed) print(self.name .. " is driving at speed "..speed) end pinto = Car.new("Pinto") trabi = Car.new("Trabant") pinto:drive(30) trabi:drive(25) --You can add methods to the class after instances of it already exist function Car:stop() print(self.name .. " has stopped.") end pinto:stop() --You can add methods to instances without affecting the original class function pinto:explode() print(self.name .. " has exploded :(") end pinto:explode() trabi:explode() --You can hide the constructor by defining it in the child function pinto.new(name, year) return setmetatable({name = name, year = year}, {__index = pinto}) end yearpinto = pinto.new("Yearly Pinto", 2019) yearpinto:explode() print(subpinto.year) pinto:explode() print(pinto.year) trabi:explode() --getfenv and setfenv: retrieve and set the lexical scope of a closure as a table --These only work in Lua 5.1 function f() x = "This is x" return function() print("g printed: " .. x) end end g = f() print(getfenv(g).x) g() setfenv(g, {print = print, x = "This is a replacement for x"}) print(getfenv(g).x) g() --Dump the current global enviroment, including print! setfenv(1,{}) print("Hi") --Lua libraries are tables --One useful library is called coroutine factorials = coroutine.create( function() n = 1 p = 1 while true do coroutine.yield(p) n = n + 1 p = p * n end end ) print(factorials) for i=1, 100 do print(i, coroutine.resume(factorials)) end --An iterator function range(lower, upper) --wrap creates a coroutine of f and returns a resume function, i.e. coroutine.yield(f) return coroutine.wrap( function() for i = lower, upper do coroutine.yield(i) end end ) end for i in range(1, 10) do print(i) end --Coroutines as recursive iterators function bingen(n) return coroutine.wrap( function() if n == 0 then coroutine.yield(" ") elseif n == 1 then coroutine.yield("()") else for center = 1, n do for left in bingen(n - center) do for right in bingen(center - 1) do coroutine.yield("("..left..", "..right..")") end end end end end ) end for t in bingen(3) do print(t) end