Skip to content

Instantly share code, notes, and snippets.

@pancelor
Last active March 18, 2023 03:13
Show Gist options
  • Save pancelor/478c612d7cebc7576a56f78e09682e8d to your computer and use it in GitHub Desktop.
Save pancelor/478c612d7cebc7576a56f78e09682e8d to your computer and use it in GitHub Desktop.
pancelor slapdash lua tutorial

(most important stuff first, trailing off into less relevant stuff towards the end. I tried to keep it short!)

toc

tables

normal tables

also called dictionaries/records/hashmaps/objects/etc by other languages

local tab_a = {hp=3,x=14,y=17} --a table with keys 'hp', 'x', and 'y'
print(tab_a.hp) --3
print(tab_a['hp']) --3
local key = 'hp' print(tab_a[key]) --3
tab_a.hp = nil --delete hp from the table
print(tab_a.bamboo) --nil, the key does not exist. but this is not an error

arrays

surprise, these are also tables, just with integer keys. but it's useful to call them "arrays"

local tab_b = {10,20,30} --keys 1,2,3 (note: lua arrays are 1-based; it's generally best to just go along with it)
print(tab_b[1]) --10
print(tab_b[2]) --20
print(tab_b[3]) --30
print(tab_b[0]) --nil
print(tab_b.elephant) --nil

append to the end with table.insert(tab_b,40) or tab_b[#tab_b+1]=40. I might type add(tab_b,40) sometimes b/c I'm used to pico8. (I have a helper for that)

iterating

for ii=1,#tab_b do
  print(tab_b[ii])
end
for ii,entry in ipairs(tab_b) do
  print(entry)
end

# returns an array's length; it only works on array-like tables (keys 1..n). if you try it on an array with "holes" you'll get very confusing results

pairs is like ipairs. pairs works on all tables but has no guaranteed iteration order. ipairs only works on arrays and goes up 1..#array

you can write custom iterators to use instead of pairs/ipairs

debugging

assert(myvar == 2,'oh no, myvar was '..tostring(myvar)..' instead of 2') -- very useful to check your assumptions
assert(myvar == 2,qq('got',myvar,'want',2)) -- qq() is my string-building helper
assert(myvar,'myvar was nil/false')

print(3) -- '3', hooray!
print({x=3,y=4}) -- 'table: 0x55c54b8dd110'  -- not very helpful!!!!
pq({x=3,y=4}) -- '{x=3, y=4}' yay! pq is my own custom thing; I consider it essential. its in printh.lua along with other helpers
pq(tab_a,tab_b,nil,'whaddup',tab_c) -- can print multiple; handles nil fine.

nil

every variable is nil until it isn't

assert(my_unset_var==nil) -- it's just nil, this line doesn't cause an error
local my_unset_var=3
assert(my_unset_var~=nil)
my_unset_var=nil --now its nil again

truthiness

nil is very cool, basically every variable is a Maybe/Optional type (e.g. from haskell, rust, others)

local result = maybe_do_something()
if result then
    result.act()
end

result here is either nil or some sort of object, and you can check if it exists with if.

if/and/or all check the "truthiness" of a variable: nil and false are falsey, literally everything else is truthy (including 0, "", {})

trick: ternaries

a = cond and b or c -- this is like if cond then a=b else a=c end. but be very careful -- if b is nil or false, a will always equal c. (and and or don't return true or false, they return the first/last argument they took that caused them to resolve. I didn't explain that well but it lets you do this ternary stuff)

trick: optional args

function callme(x,y, border) -- the extra space is my personal convention
    border = border or 8
end

if border is not passed, e.g. callme(2,3) then border will be nil, until the first line sets it to 8

variables are references

all variables names are references that point to a value. assigning a variable assigns the value on the righthand side into the name on the lefthand side. this can cause subtleties you might not expect:

a=1
b=a
a=1000
assert(b==1)

a={x=0}
b=a -- b and a now refer to the same table. the "value" of a is... sorta like a pointer to the table, not the table itself
print(a) --table: 0x55c54b8dd110
print(b) --table: 0x55c54b8dd110
a.x=1000
assert(b.x==1000)

multivals

function hello(x,y)
  return x+y,x*y
end
local c,d = hello(20,30)
assert(c==50)
assert(d==600)

cute, useful, but pretty cursed if you try to get too fancy:

function nums()
  return 1,2
end
function feedme(a,b,c)
  print(a)
  print(b)
  print(c)
end

feedme(1,2,3) --1,2,3
feedme(4,nums()) --4,1,2 --yo cool
feedme(nums(),3) --1,3,nil !?! (multivals only work as the last arg)

syntax, other misc

lots of info in the lua manual -- this is 5.1, which is what luajit uses, which is what love2d uses. its a bit old and I wish we used lua 5.4 but we do not.

colon syntax: : versus .

table:foo(x,y) is syntax sugar for table.foo(table,x,y).

function thingy:bar(x,y) is syntax sugar for thingy.bar = function(self,table,x,y). so you'll see self a lot, it's not particularly special to the lua interpreter, but it's what that first argument gets named so its convenient to use it. and text editors will prolly highlight it special. you can do weird stuff like thingy.bar(other_thing,x,y) (note: ., not :) but idk why you would want to

very common hard-to-track-down bug: saying thingy.foo() when you meant to say thingy:foo(). I dunno any good way of catching that. some sort of linter program?

static types

no static typing, sorry. this causes bugs sometimes. I test the stuff I'm working on often and it works well enough

you can change the type of a variable but please do not:

local a="hello"
a=42
a={x=3}

assert(type(3)=="number") basic type checking. use very sparingly, a bit of a code-smell to me

numbers

assert(2.0==2) all numbers are floats (doubles? not sure). bitwise operations (& | ~ << etc) only work on numbers that happen to be integers -- error otherwise, annoying. also bitwise ops don't work in html exports, so I'm avoiding them for now

syntax

if foo and foo.time==4 then
elseif bar~=2 then --not equals
elseif not qux then
-- elseif not bar==4 then -- don't do this!!! it checks (not bar)==4, oh no
else
end

while thing do
end

function foo(a,b)
end

there's no switch statement

there's no continue statement inside for loops. (there is a break tho). you can use ::name:: ... goto name except probably don't b/c that breaks in html exports

varargs: ...

function stuff(x,y,...)
  local as_table={...}
  return as_table[3]
end
print(stuff(1,2,10,20,30,40,50)) --30

the last arg can be ..., for extra "varargs", which are a multival. you can do {...} to turn it into a table. its niche but hard to google so I'm putting it here

increment operator etc: +=

I wish lua had x += 1 but it does not. you gotta x = x+1 instead

random numbers

math.random is kinda wack (sometimes returns floats, sometimes returns ints), I have helpers that I use instead

require 'foo'

local myvar = require 'src/hello' is a function that runs the file src/hello.lua (path is relative to main.lua) and stores the result (the top-level return, or nil) in myvar. anything declared globally in hello.lua will be put in the global namespace, files don't have separate namespaces.

syntax sugar note: the parens in the function call foo('hello') are unecessary. this only applies when the function takes a single arg and that arg is a literal string or table. e.g. foo'hello' or foo{x=3,y=4}. it's odd. but anyway that's how require 'foo' works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment