Lab 3 - Lua
This lab will introduce you to the logistics of programming in Lua. To get the starter code for this lab, go to the assignments repository and run git pull, then go into the lab3 directory.
Installation
Follow the setup instructions in Assignment 8.
To run a Lua file, it’s as simple as lua your_file.lua.
Basics
Most of Lua should look pretty familiar to you as a standard imperative, dynamically-typed language.
local primitive_types = {
0, -- number
false, -- boolean
"hello", -- string
nil -- nil
}
Functions are first-class values.
local f = function(n) return n + 1 end
assert(f(1) == 2)
local do_twice = function(f)
f()
f()
end
do_twice(function() print('hello!') end)
Control flow is the usual ifs and loops.
for i = 1, 10 do
if i % 2 == 0 then
print(i)
end
end
Most of the language’s complexity and power arises around tables.
local t = {}
t["key"] = "value"
assert(t["key"] == "value")
Lua’s iterators are a common way to deal with tables.
local t = {}
t[1] = "a"
t.b = "c"
-- pairs gets all key/value pairs in the table
-- prints (1, a) and (b, c)
for k, v in pairs(t) do
print(k, v)
end
-- ipairs gets the integer key/value in the table
-- prints (1, a)
for k, v in ipairs(t) do
print(k, v)
end
Lastly, a quick recap on a few pieces of syntactic sugar that Lua provides.
local t = {}
-- These are all the same
t.f = function(self, x) end
function t.f(self, x) end
function t:f(x) end
-- These are both the same
t.f(t, 1)
t:f(1)
Exercises, part 1
To warm up with Lua’s datatypes, let’s dive into an example of Lua’s reflective powers by implementing an auto-serializer. One of Python’s most useful features is its pickle module that enables you to serialize almost any Python object into a string and back. Let’s write a simple equivalent for Lua! The goal is that, for any value v, we have that deserialize(serialize(v)) == v where serialize returns a string.
In part1/serialize.lua, your task is to implement the serialize and deserialize functions. Your serializer only needs to work for numbers, strings, and tables containing keys/values that are numbers/strings/tables. Moreover, you can assume the strings will not contain parentheses. A few hints:
- To perform the necessary introspection, you will need the
typefunction. - We have provided you a function
parse_parensthat takes a string with a sequence of balanced parentheses, and returns a list of sub-strings. For example,"(a)((b)(c))(d)"parses to{"a", "(b)(c)", "d"}. This should suggest a serialization strategy! tonumberconverts a string to a number.string.formatdoes string formatting.
Metatables
As we saw in lecture on Wednesday, metatables enable us to override behaviors of tables. We looked specifically at the __index metamethod that is called whenever a table has a missing index.
local t = {a = 1}
setmetatable(t, {__index = function(t, k)
return 0
end})
assert(t.a == 1)
assert(t.b == 0)
Today, I want to briefly highlight a second metamethod that you will use in the assignment: __newindex. This metamethod is called when assigning a value to a key that doesn’t exist yet. For example:
local t = {a = 1}
setmetatable(t, {__newindex = function(t, k, v)
rawset(t, k, v + 1)
end})
t.a = 2
t.b = 2
assert(t.a == 2)
assert(t.b == 3)
This function adds one to all values being assigned to the table for the first time. Because t.a is already defined, newindex is not called for t.a = 2, but it is called for t.b = 2. Note that we have to use rawset, which means “set this key but bypass any __newindex” because otherwise doing t.k = v + 1 would cause an infinite recursion.
Exercises, part 2
In part2/metatables.lua, we have two short exercises for you.
-
Implement a function
guard(t, keys)that takes as input a table and a list of keys, and augments the table such that newly inserted values must have a key in the providedkeyslist. Attempting to set an invalid key should raise an error. -
Implement a function
multilink(t, parents)that dispatches lookups for missing keys to a list of parent tables in left-to-right order.
For both functions, see the tests below the function for usage examples.
C API
Read through the Lua C API and C->Rust sections of the assignment.
Exercises, part 3
For the final exercise, you will implement a short string matching function in Rust that you can call in Lua. In part3/stringmatch.rs, we have provided you with a skeleton for a Rust-style Lua library containing a single function, stringmatch.
stringmatch should be a Lua function that takes two arguments, a string and a pattern, and returns the number of times that pattern appears in the string. For example:
assert(stringmatch.match("abc", "a") == 1)
assert(stringmatch.match("aaa", "a") == 3)
assert(stringmatch.match("abc", "d") == 0)
assert(stringmatch.match("hello world", "world") == 1)
From the Lua API, you will need to use luaL_checkstring and lua_pushnumber to implement this function. You should also investigate the CStr API for dealing with C string pointers in Rust. Run make to build the Rust file into a Lua library.
In order to build you will need to rename a file depending on whether you are on Linux or macOS.
# Linux
mv lauxlib.linux.rs lauxlib.rs
# macOS
mv lauxlib.macos.rs lauxlib.rs