Introduction to the Lua Programming Language


Author: Luc V. Talatinian

CPS 499-03: Emerging Languages, Spring 2017


Key language concepts in Lua

  • Written in C
    • Can be used on top of another C-based program or interpreted by itself
    • Generally no main() function in first case
    • Access portions of C memory (read-only) using userdata type
  • Dynamic typing
  • Types: nil, boolean, number, string, function, userdata, thread, table
  • All values are first-class
  • Table-based data
    • All data is represented in tables
    • Classic arrays (index starts @ 1) and 'objects'
  • Automatic garbage collection


Core Lua

  • Types
    • Dynamic and first class for all types
    • NaN represents undefined math results (i.e. 0/0)
    • nil is analogous to null in C
    • nil and false represent logical false (0 does not!)
  • Tables
    • In short: a list of key-value pairs
      • Heterogeneous in both key and value
      • Can index by any value except nil and NaN
    • Data can be defined in tables
    • Standard arrays (index from 1...n)
    • Can define our own objects using tables
    • Metatables: tables of functions that define ops for other tables
      • Operations such as + (__add), length (__len), comparison (__eq)
      • Similar to operator overloading in C
      • We can create and set our own metatables
  • Coroutines
    • Similar to threading in other languages
    • Non-preemptive


Brief overview of types in Lua

  • Everything is first-class
  • Limit scope using local keyword
  • nil: unequal to everything but itself
  • boolean: classical true/false (comparison ops are literal and, or, not)
  • number: two subtypes (recent addition to language) int and float
  • string: use literal .. for concatenation
  • function: can declare functions or assign them to variables
    -- these two are equivalent
    function f(a)
        print(a)
    end
    local f = function(a)
        print(a)
    end
    
  • userdata: data from running C program (rd-only)
  • thread: used in coroutines
  • table: powerful data storage mechanism (see below)


Tables

  • Extremely versatile due to implementation
  • Heterogenous: can store data in a table by any index (not NaN/nil) and values can be different types
    local arr = {'str1', 'str2', 'str3'} -- standard array (index starts at 1, built-in lua functions account for this)
    local tab = {
        key1 = 123,
        key2 = 'data_str',
        [1] = function()
            print('our func at index 1')
        end
    }
    
    for k,v in ipairs(arr) do -- iterate through key/value pairs in a table
        print(k .. ' ' .. v)
    end
    
    print(tab.key1)
    print(tab.key2)
    tab[1]()
    
  • We can represent any data struct (stack, queue, etc.) using tables
    • Can define one-off arbitrary objects (think Javascript)
    • Can create 'prototypes' for classes as well
    • Can define ops for our structures with metatables
  • Can create a "package" of functions (standard Lua libraries are implemented this way, such as the os.() family)
  • Table referencing:
    tab['str'] == tab.str -- syntactic sugar
  • Metatables
    • Tables of functions that define various Lua operations for other tables
    • Some examples: addition __add, equality __eq, and many others
    • setmetatable(table, metatable) allows us to define our own metafunctions for tables
      • Extremely useful for function prototyping (think operator overloading)
      • See below example for use of metatable in a simple class


Counter example

  • Define prototype for instantiating a simple counter class
  • Provide one metamethod: __eq (equality), two counters are equal if their val and step are the same
  • Counter = {} -- empty table
    Counter.__eq = function(a, b)
        print('our metamethod was called')
        return a.val == b.val and a.step == b.step
    end
    
    Counter.new = function(init, step)
        local new_counter = {} -- populate this with kv-pairs and return
    	setmetatable(new_counter, Counter) -- we supply our metamethods
    
        new_counter.val = init
        new_counter.step = step
        new_counter.inc = function(self) -- note need for reference to self
            self.val = self.val + self.step 
        end
        new_counter.show = function(self)
            print(self.val)
        end
    
    	return new_counter
    end
    
    local c1 = Counter.new(10, 2)
    c1.show(c1) -- 10
    c1.inc(c1)
    c1:show() -- 12, syntactic sugar: auto-pass reference to self
    c1:inc()
    c1:show() -- 14
    
    local c2 = Counter.new(14, 2)
    print(c1 == c2) -- true
    
    local c3 = Counter.new(10, 2)
    print(c2 == c3) -- false
    
    -- no concept of private/public data in tables, we can
    -- access and modify counter values directly
    c1.inc = function()
        print('we broke the object')
    end
    c1:inc()
    


Coroutines

  • Mechanism similar to multi-threading
  • NOT true multi-threading
    • Coroutines are non-preemptive by default (not scheduled by OS)
    • Coroutine execution is paused only by coroutine.yield()
    • Can write your own scheduler method for coroutine management
  • Created using coroutine.create(), takes a function to be executed
  • Coroutines begin in suspended state, start (or restart after yield) using coroutine.resume()
  • Can check status with coroutine.status() (returns a string: running, dead, or suspended)
  • Example: infinite ping-pong
  • function ping()
    	local pong = coroutine.create(function() -- note that 'coroutine' is a table with all the functions we need
            while true do
                print('pong')
                coroutine.yield()
            end
        end)
    
        while true do
            print('ping')
            coroutine.resume(pong)
        end
    
        print(coroutine.status(pong))
    end
    
    ping()
    
  • Using two coroutines:
  • ping = coroutine.create(function()
        while true do
            print('ping')
            coroutine.resume(pong)
        end
    end)
    
    pong = coroutine.create(function()
        while true do
            print('pong')
            coroutine.yield()
        end
    end)
    
    coroutine.resume(ping)
    


Exercises

The following are some programming exercises that incorporate some essential Lua concepts:
  • (Tables) Define a time class in Lua using a metatable and object prototype function.
    • A time object stores a 24-hour time value (this could be done several ways).
    • Define a prototype function in your metatable called new() that returns a new instance of a time object (initially at 00:00:00).
    • Provide the following interface:
      • tick(): adds one second to the timer.
      • reset(): reset the time to 00:00:00.
      • set(t): sets the value to the new time immediately (input can be either a string of the form hh:mm:ss or an integer representing the number of seconds since 00:00:00).
      • format(): returns a string of the current time value formatted in the style hh:mm:ss.
    • Provide the following functionality for each time object through the metatable:
      • Addition, subtraction between times (must account for overflow)
      • Comparison
      • Indexing: t[1] gives the hours, t[2] the minutes, and t[3] the seconds (may not require a metamethod depending on your implementation)

  • (Coroutines) Create a function spawn_fact_chain(n) that sequentially spawns a variable number of coroutines (specified by n) that print (in order) the result of factorial(1) to factorial(n), in the order that each coroutine is created. Your implementation must avoid recomputation of the factorial across coroutines (i.e. you should only perform one multiplication per coroutine, minus the first).

  • (Program extension) Familiarize yourself with the Lua interface to vim (here). Create a Lua script that when run in vim will insert a new C-style comment-block (see format below) time-stamped. See the example below for formatting of the comment block. Use of your solution from problem 1 with some minor changes to the prototype and format() functions is recommended, but not required.
    • When complete, you should be able to run the command :luafile [your script file] for the desired effect.
    • Note that you must be using vim for Lua support; the original vi editor did not include Lua functionality.
    • Example transformation:
    • #include <iostream>
      using namespace std;
      
      int main(int argc, char** argv)
      {
          printf("supplied argument count is %d", argc);
      }
      
      // after running ':luafile [script]' w/ cursor on line 5:
      
      #include <iostream>
      using namespace std;
      
      int main(int argc, char** argv)
      {
          /* 22 Mar 2017 [13:57:00]
           */
          printf("supplied argument count is %d", argc);
      }
      


References

    R. Ierusalimschy, L. Henrique de Figueiredo, W. Celes. Lua 5.3 Reference Manual. Lua.org, PUC-Rio, 2017.
    R. Ierusalimschy. Programming in Lua. Lua.org, December 2003.

Return Home