July 10, 2022

I've decided to start reading this book to get more insight into how to incorporate Lua into Neovim

Contents

Chapter 1

Getting Started

-- file test.lua
print("Hello World")

-- defines a factorial function
function factorial(n)
  if n == 0 then
    return 1
  else
    return n * factorial(n-1)
  end
end

print("enter a number:")
a = io.read("*n")
print(factorial(a))

-- types
type(nil) --> nil
type(true) --> boolean

You can run the code above by passing the test.lua file to the Lua interpreter of your choice.

luajit test.lua

# If you want to open an iterative session after running the code, use the
# -i flag. This is usually helpful for debugging.
luajit -i test.lua

List of reserved words:

List of basic types:

type(a) --> nil. It does not return an error.
a = 10
type(a) --> number
a = "string"
type(a) --> string

Exercises

Run the factorial example. What happens to your program if you enter a negative number? Modify the example to avoid this problem.

A: The program raises a stack overlow error.

factorial.lua

#!/usr/bin/luajit

function factorial(n)
  if n < 0 then
    return nil
  elseif n == 0 then
    return 1
  else
    return n * factorial(n-1)
  end
end

print("enter a number:")
a = io.read("*n")
print(factorial(a))

Write a simple script that prints its own name without knowing it in advance.

#!/usr/bin/luajit

function print_file_name()
  print(arg[0])
end

print_file_name()

Chapter 4

Strings

-- The length operator is `#`
a = "hello"
print(#a) --> 5
print(#"test") --> 4

-- the lib string also has a length function
print(string.len("morning!")) --> 8

-- String concatenation happens with `..`
print("hello " .. "world")

-- long strings use [[ and ]]
body = [[
<body>
  <a href="https://marcelofern.com">Marcelo Fern</a>
</body>
]]
-- or [[= (followed by any number of = signs)
-- and =]]. This is useful when you need to
-- stringfy text that includes ]]
text = [[===
This text has ]]
===]]
-- coercions
print(10 .. 20) --> 1020

-- string to number
tonumber("   -3 ") --> -3
tonumber(" i2 ") --> nil

-- number to string
tostring(3) --> "3"

-- formatting a string
string.format("x = %d y = %d", 10, 20) --> x= 10 y = 20

-- search in string
x, y = string.find("hello world", "wor") --> 7 9
string.find("hello world", "war") --> nil

Chapter 5

Tables

Tables are the only data structuring mechanism in Lua.

#!/usr/bin/luajit

a = {}
a["x"] = 10
print(a["x"]) --> 10

b = {}
b.x = 20
print(b.x) --> 20

Implementing lists / arrays:

numbers = {10, 20, 30, 40, 50}
print(numbers[1]) --> 10 Indexes in Lua start at 1.
print(#numbers) --> 5 The # calculates the length.

-- note that # is unreliable for lists with holes.

-- iterate over an array
for i=1, #numbers do
  print(numbers[i])
end

-- or use ipairs
for k, v in ipairs(numbers) do
  print(k, v)
end
--> 1 10
--> 2 20
--> 3 30
--> 4 40
--> 5 50

-- append an element at the end of an array
numbers[#numbers + 1] = 60
print(numbers[6]) --> 60
-- or use table.insert
table.insert(numbers, 70)
print(numbers[7]) --> 70

-- remove the last element of an array, returning it as a result
print(table.remove(numbers)) --> 70
print(numbers[#numbers]) --> 60
-- you can also call `table.remove` with
-- the number of the index you want to remove.
-- in that case, the holes will be filled up
-- i.e., each element after the removed one
-- will be brought one position down.

Implementing a dictionary

name = {first="Marcelo", last="Fernandes"}
print(name.first .. " " .. name.last) --> Marcelo Fernandes

-- traverse all key-value pairs
for k, v in pairs(name) do
  print(k, v)
end
--> first Marcelo
--> last Fernandes

name.first = nil --> Remove the key from the table.

-- To use strings:
sum = {["+"] = "add"}
print(sum["+"]) --> "add"

Chapter 6

Functions

function add(array)
  local sum = 0
  for i=1, #array do
    sum = sum + array[i]
  end
  return sum
end

print(add({20, 20})) --> 40

Variadic functions (variable number of arguments)

function add(...)
  local sum = 0
  for _, v in pairs({...}) do
    sum = sum + v
  end
  return sum
end

print(add(3, 4, 5, 6, 7)) --> 25

Exercices

Write a function that takes an array and prints all its elements

#!/usr/bin/luajit

function print_array(array)
  print(unpack(array))
end

print(print_array({3, 4, 5, 6, 7}))

Write a function that takes an arbitrary number of values and returns all of them, with the exception of the first one.

function all_but_first(first, ...)
  return ...
end

print(all_but_first(1, 2, 10)) --> 2 10
print(all_but_first(1, "5", "word")) --> "5" "word"

Write a function that takes an arbitrary number of values and returns all of them, with the exception of the last one.

#!/usr/bin/luajit

function all_but_last(...)
  array = {...}
  table.remove(array)
  return unpack(array)
end

print(all_but_last(1, 2, 10)) --> 1 2
print(all_but_last(1, "5", "word")) --> 1 "5"

Chapter 18

Generic for

-- given a function to iterate
-- over an array using closures
function array_iterator(array)
  local i = 0
  return function()
    i = i + 1
    return array[i]
  end
end

-- you can use a while loop
array = {10, 20, 30}
iterator = array_iterator(array)
while true do
  element = iterator()
  if element == nil then
    break
  print(element)
  end
end

-- But you could use the generic for
for element in array_iterator(array) do
  print(element)
end

Stateless iterators:

#!/usr/bin/luajit

local function array_iterator(array, index)
  index = index + 1
  local value = array[index]
  if value then
    return index, value
  end
end

local function stateless_array_iterator(array)
  return array_iterator, array, 0
end

array = {10, 20, 30}

for index, element in stateless_array_iterator(array) do
  print(index, element)
end

Chapter 20

Metatables and metamethods

-- setting and getting metatables
table = {}
second_table = {}

setmetatable(second_table, table)
print(getmetatable(second_table) == table) --> true

-- a table can be its own metatable

setmetatable(table, table)
print(getmetatable(table) == table) --> true

Chapter 21

Object-Oriented Programming

Whenever a : is present on a function name, Lua injects a self keyword in the body of the function:

Account = {balance=0}
function Account:withdraw(amount)
  self.balance = self.balance - amount
end

acc = Account
acc:withdraw(10)
print(acc.balance) --> -10

-- the above is only syntax sugar.
-- you can achieve the same as:
Account = {balance=0}
function Account.withdraw(self, amount)
  self.balance = self.balance - amount
end

acc = Account
-- pass the extra argument here.
acc.withdraw(acc, 10)
print(acc.balance) --> -10

Creating classes

-- Lua uses the idea of prototypes like
-- JavaScript. To set the prototype of
-- an object, do this:
setmetatable(A, {__index = B})
-- now A looks up to B for
-- all operations that it does
-- not have.

Thus we can create account objects:

-- defaults
Account = {balance=0}

-- methods
function Account:withdraw(amount)
  self.balance = self.balance - amount
end

-- constructors
local mt = {__index = Account}
function Account.new(o)
  o = o or {}
  setmetatable(o, mt)
  return o
end

acc = Account.new()
acc:withdraw(50)
print(acc.balance) --> -50

You can improve this by setting the prototype in the constructor directly

-- defaults
Account = {balance=0}

-- constructors
function Account:new(o)
  o = o or {}
  -- use the Account table
  -- as the instance prototype
  self.__index = self
  setmetatable(o, self)
  return o
end

-- methods
function Account:withdraw(amount)
  self.balance = self.balance - amount
end


acc = Account:new()
acc:withdraw(70)
print(acc.balance) --> -70

Inheritance:

#!/usr/bin/luajit

-- defaults
Account = {balance=0}

-- constructors
function Account:new(o)
  o = o or {}
  -- use the Account table
  -- as the instance prototype
  self.__index = self
  setmetatable(o, self)
  return o
end

-- methods
function Account:withdraw(amount)
  if amount > self.balance then
    error("insufficient funds")
  end
  self.balance = self.balance - amount
end

-- The SpecialAccount allows users
-- to withdraw more than they own

-- defaults
-- SpecialAccount = an account object with
-- the Account prototype.
SpecialAccount = Account:new()

-- methods
-- override withdraw
function SpecialAccount:withdraw(amount)
  if (amount - self.balance) >= self:get_limit() then
    error("insufficient funds")
  end
  self.balance = self.balance - amount
end

function SpecialAccount:get_limit()
  return self.limit or 0
end

special_acc = SpecialAccount:new({limit=1000})
special_acc:withdraw(999)
print(special_acc.balance) --> -999

-- this line returns an error
special_acc:withdraw(1000)