기본
Hello World
print는 자동으로 줄바꿈을 추가합니다
-- comments start with --
print("Hello, World!")
-- variables (global by default)
x = 10
name = "Alice"
-- local variables
local y = 20
local z = x + y
-- multiple assignment
local a, b, c = 1, 2, 3
print(a, b, c) -- 1 2 3주석
--[[ ]]는 여러 줄 주석용
-- single line comment
--[[ multi-line
comment block ]]
print("hi")
-- long-bracket comments can have = signs for nesting
--[==[ this is a comment with ]] inside ]==]
print("still runs")선택적 세미콜론
세미콜론은 선택 사항입니다
print("Hello") -- prints with a trailing newline
io.write("no newline") -- writes without a newline
-- print separates multiple args with tabs
print("name", "age") -- name age
-- print converts all types (nil included)
print(42, true, nil) -- 42 true nil
-- io.stdout / io.stderr are file handles
io.stderr:write("warning\n")다중 할당
여러 변수에 동시 할당을 지원합니다
local a, b = 1, 2
print(a, b) -- 1 2
a, b = b, a -- swap without a temp variable
print(a, b) -- 2 1
local x, y, z = 10 -- y, z become nil
local m, n = 1, 2, 3 -- 3 is discarded
print(m, n) -- 1 2대화형 모드
lua -i는 대화형 모드로 진입합니다
-- run the REPL: lua -i
-- run a file: lua script.lua
-- show the version: lua -v
-- inside the REPL:
-- > print("hi")
-- hi
-- > = 2 + 3 -- '=' prints an expression value
-- 5
-- > = string.rep("ab", 3)
-- abababnil & Variable Deletion
nil is Lua's only 'no value' type. Assigning nil to a table key deletes it. Accessing undeclared globals returns nil rather than raising an error, which can mask typos—use strict.lua to catch them.
local x = 10
print(x) -- 10
x = nil -- delete the value
print(x) -- nil
if x == nil then
print("x is nil")
end
-- reading an undeclared name returns nil (no error)
print(undefined_var) -- nil
-- assign nil to let the GC collect a table entry
local t = {a = 1}
t.a = nil변수
local 변수
local은 지역 변수를 선언합니다
-- Lua has 8 basic types:
print(type(nil)) -- nil
print(type(true)) -- boolean
print(type(42)) -- number
print(type("hi")) -- string
print(type({})) -- table
print(type(print)) -- function
print(type(coroutine.create(function() end))) -- thread
print(type(io.stdin)) -- userdata
-- type() always returns a string
print(type(type(42))) -- string전역 변수
local 없이는 전역 변수입니다
-- Lua 5.3+ distinguishes integers and floats
local i = 10 -- integer
local f = 3.14 -- float
print(type(i), math.type(i)) -- number integer
print(type(f), math.type(f)) -- number float
local hex = 0xFF -- 255
local exp = 1e3 -- 1000.0 (float)
print(hex, exp)
-- integer / integer with no fraction stays integer
print(7 / 2) -- 3.5 (true division, always float)
print(7 // 2) -- 3 (floor division)다중 할당
누락된 값은 nil이 되고, 초과는 버려집니다
local s1 = "double quoted"
local s2 = 'single quoted' -- identical to double
local s3 = [[
a multi-line
string, escapes ignored]]
print(#s1) -- 14 (# is the length operator)
print(s1:upper()) -- DOUBLE QUOTED
-- strings are immutable: operations return new strings변수 교환
임시 변수가 필요 없습니다
local flag = true
local done = false
-- ONLY nil and false are falsy
-- 0, "", and {} are all truthy (unlike many languages)
if 0 then print("0 is truthy") end
if "" then print("empty string is truthy") end
-- logical operators short-circuit and return a value
local x = nil or "default" -- "default"
local y = true and "yes" -- "yes"
print(x, y)변수 삭제
nil을 할당하면 GC가 회수할 수 있습니다
-- the table is Lua's only composite data structure
-- it serves as array, dictionary, object, and module
local arr = {10, 20, 30} -- array (1-indexed)
local dict = {name = "Lua", ver = 5.4}
local mixed = {1, 2, x = 3}
print(arr[1]) -- 10 (indices start at 1, not 0)
print(dict.name) -- Lua
print(mixed.x) -- 3
print(#arr) -- 3Type Checks
type() is the standard way to branch on a value's type. It returns 'nil' for nil rather than erroring. For finer checks use math.type (int vs float) or rawequal for identity comparison.
local function describe(v)
local t = type(v)
if t == "table" then
return "a table"
elseif t == "string" then
return "a string: " .. v
elseif t == "nil" then
return "nothing"
end
return "a " .. t
end
print(describe({})) -- a table
print(describe("hi")) -- a string: hi
print(describe(nil)) -- nothing데이터 타입
기본 타입
Lua에는 8개의 기본 타입이 있습니다
print(10 + 3) -- 13
print(10 - 3) -- 7
print(10 * 3) -- 30
print(10 / 3) -- 3.3333... (true division)
print(10 % 3) -- 1 (modulo)
print(2 ^ 10) -- 1024.0 (^ is exponent, NOT xor)
print(-5) -- -5 (unary minus)숫자
Lua 5.3+은 정수와 부동소수점을 구분합니다
-- Lua 5.3+ integer floor division
print(10 // 3) -- 3 (integer)
print(-10 // 3) -- -4 (floors toward -inf)
print(10.0 // 3) -- 3.0 (float floor div)
-- modulo result takes the sign of the divisor
print(10 % 3) -- 1
print(-10 % 3) -- 2
print(10 % -3) -- -2문자열
#는 문자열 길이를 가져옵니다
print(3 == 3) -- true (equality)
print(3 ~= 4) -- true (inequality: ~=, not !=)
print(3 < 4) -- true
print(3 > 4) -- false
print(3 <= 3) -- true
print(3 >= 4) -- false
print("abc" < "abd") -- true (lexicographic)
print(1 == "1") -- false (different types never equal)불린
0과 빈 문자열은 참입니다
-- and / or / not short-circuit and return a value (not a bool)
print(true and 10) -- 10
print(false or "x") -- "x"
print(nil or "default") -- "default"
print(not nil) -- true
-- idiom for defaults
local name = nil
local display = name or "anonymous"
-- ternary-like (beware falsy 'false' values!)
local v = (cond) and "yes" or "no"테이블
table은 유일한 복합 데이터 구조입니다
local name = "Lua"
local v = 5.4
print("Hello " .. name .. " " .. v) -- Hello Lua 5.4
-- .. always creates a new string
local s = "a" .. "b" .. "c"
print(s) -- abc
-- numbers are auto-converted when concatenated
print("n=" .. 42) -- n=42
-- repeat a string with string.rep
print(string.rep("ab", 3)) -- abababLength Operator
# returns the byte length of a string or the length of a table's sequence part. It is only well-defined for tables without holes (nil gaps). For UTF-8 character counts use lua-utf8 or the utf8 library (Lua 5.3+).
-- # works on strings and the sequence part of tables
print(#"hello") -- 5
print(#{10, 20, 30}) -- 3
-- caution: # only counts the contiguous sequence part
-- the result is UNDEFINED if the table has holes
local t = {1, 2, nil, 4}
print(#t) -- 2 or 4 (implementation-defined)
-- # on a string returns its byte length, not codepoints
print(#"cafe") -- 4
print(#"café") -- 5 (é is 2 bytes in UTF-8)연산자
산술 연산자
^는 XOR이 아닌 거듭제곱입니다
local score = 85
if score >= 90 then
print("A")
elseif score >= 80 then
print("B")
elseif score >= 70 then
print("C")
else
print("F")
end
-- keywords: if / then / elseif / else / end
-- parentheses around the condition are optional바닥 나눗셈
//는 바닥 나눗셈입니다
local i = 1
while i <= 5 do
print(i)
i = i + 1 -- Lua has no ++ operator
end
-- the condition is checked before each iteration
local n = 0
while n < 3 do
print("loop", n)
n = n + 1
end비교 연산자
~=는 같지 않음을 의미합니다
-- repeat-until runs the body at least once (like do-while)
-- the loop STOPS when the condition becomes true
local i = 1
repeat
print(i)
i = i + 1
until i > 5
-- the condition can see locals declared in the body
local x
repeat
x = math.random(1, 10)
until x == 7논리 연산자
and/or는 단락 평가를 지원합니다
-- for start, stop[, step]
for i = 1, 5 do
print(i) -- 1 2 3 4 5
end
for i = 10, 1, -2 do
print(i) -- 10 8 6 4 2
end
-- step defaults to 1; the loop variable is local to the loop
for i = 1, 3 do print(i) end
-- print(i) -- i is out of scope here문자열 연결
..는 문자열을 연결합니다
local arr = {"a", "b", "c"}
-- ipairs iterates the array (sequence) part, 1..n
for i, v in ipairs(arr) do
print(i, v) -- 1 a / 2 b / 3 c
end
local dict = {x = 1, y = 2}
-- pairs iterates ALL key/value pairs (order not guaranteed)
for k, v in pairs(dict) do
print(k, v)
end길이 연산자
#는 문자열이나 테이블의 길이를 가져옵니다
-- break exits the innermost loop only
for i = 1, 10 do
if i == 5 then break end
print(i) -- 1 2 3 4
end
-- Lua has NO continue; emulate it with goto
for i = 1, 5 do
if i % 2 == 0 then goto continue end
print(i) -- 1 3 5
::continue::
end
-- goto jumps to a label ::name:: within the same scope제어 흐름
if-elseif-else
then과 end 키워드에 주의하세요
local s1 = "double 'quoted'"
local s2 = 'single "quoted"'
local s3 = "line1\nline2\ttabbed"
local s4 = "percent: %d %%"
-- long brackets: escapes are NOT processed
local s5 = [[raw \n string, \t kept literal]]
print(s3, s5)while 루프
while-do-end
local s = "hello"
print(#s) -- 5 (byte length)
-- the string library is 1-indexed
print(s:sub(1, 3)) -- "hel"
print(s:sub(-2)) -- "lo" (negative = from end)
print(s:byte(1)) -- 104 (ASCII of 'h')
print(string.char(104, 105)) -- "hi"repeat-until
do-while과 같으며, 조건이 참일 때 종료됩니다
-- [[ ]] preserves newlines exactly
local text = [[Line 1
Line 2
Line 3]]
print(text)
-- match bracket level to allow ]] inside
local code = [==[
if a then ]] -- this ]] is literal
end ]==]
print(code)수치형 for
for 시작, 종료, 스텝
local n = tonumber("42") -- 42 (number)
local f = tonumber("3.14") -- 3.14
local bad = tonumber("abc") -- nil (no error raised)
local s = tostring(42) -- "42"
local b = tostring(true) -- "true"
local nil_s = tostring(nil) -- "nil"
print(tonumber("0x1F")) -- 31 (hex)
print(tonumber("11", 2)) -- 3 (base 2)일반형 for
ipairs는 배열을 반복, pairs는 모두 반복
-- string.format uses C-style format strings
print(string.format("%d + %d = %d", 2, 3, 5)) -- 2 + 3 = 5
print(string.format("%.2f", 3.14159)) -- 3.14
print(string.format("%5d", 42)) -- " 42"
print(string.format("%-10s|", "hi")) -- "hi |"
print(string.format("%x %o", 255, 8)) -- ff 10break
Lua에는 continue가 없습니다
local s = "Hello, World"
-- s:method(x) is sugar for string.method(s, x)
print(s:upper()) -- HELLO, WORLD
print(s:lower()) -- hello, world
print(s:rep(2)) -- Hello, WorldHello, World
print(s:reverse()) -- dlroW ,olleH
print(s:sub(1, 5)) -- Hello
print(s:find("World")) -- 8 13 (start and end indices)함수
함수 정의
local function은 지역 함수를 정의합니다
-- array (sequence)
local arr = {10, 20, 30, 40}
-- dictionary (hash)
local p = {name = "Lua", version = 5.4}
-- mixed: both sequence and hash entries
local m = {1, 2, 3, lang = "Lua"}
print(arr[1]) -- 10 (1-indexed!)
print(p.name) -- Lua
print(p["version"]) -- 5.4
print(#arr) -- 4다중 반환값
함수는 여러 값을 반환할 수 있습니다
local t = {name = "Lua", ["full name"] = "Lua 5.4"}
-- dot notation works only for valid identifiers
print(t.name) -- Lua
-- bracket notation works for any string key
print(t["full name"]) -- Lua 5.4
-- bracket form accepts variables
local key = "name"
print(t[key]) -- Lua
t.name = "LuaLang"
t["new key"] = 99가변 인자 함수
...는 가변 인자를 나타냅니다
local t = {10, 20, 30}
table.insert(t, 40) -- append: {10,20,30,40}
table.insert(t, 1, 5) -- insert at index 1: {5,10,20,30,40}
print(#t) -- 5
local removed = table.remove(t) -- pop last: 40
local first = table.remove(t, 1) -- remove index 1: 5
print(removed, first, #t) -- 40 5 3
-- table.move(src, f, e, t[, dst]) (Lua 5.3+)
local src = {1, 2, 3, 4, 5}
table.move(src, 2, 4, 1) -- copy elements 2..4 to position 1익명 함수
함수는 일급 시민입니다
local nums = {5, 2, 8, 1, 9}
table.sort(nums)
print(table.concat(nums, ",")) -- 1,2,5,8,9
-- custom comparator (return true if a should come before b)
local people = {
{name = "Bob", age = 30},
{name = "Ann", age = 25},
}
table.sort(people, function(a, b) return a.age < b.age end)
for _, p in ipairs(people) do print(p.name, p.age) end
-- Ann 25 / Bob 30인자로서의 함수
고차 함수
local t = {10, 20, 30, name = "Lua"}
-- ipairs: sequence part, stops at the first nil
for i, v in ipairs(t) do
print(i, v) -- 1 10 / 2 20 / 3 30
end
-- pairs: every key/value pair (order not guaranteed)
for k, v in pairs(t) do
print(k, v) -- includes name Lua (order varies)
end
-- numeric loop over the sequence
for i = 1, #t do print(t[i]) endTable Length & Nested Tables
Tables nest naturally—matrix[r][c] indexes a row then a column. # on a nested table gives that row's length. table.concat efficiently joins a sequence of strings (and numbers) with a separator.
local matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
print(matrix[2][3]) -- 6 (row 2, col 3)
print(#matrix) -- 3 (number of rows)
print(#matrix[1]) -- 3 (number of cols)
-- table.concat joins an array of strings/numbers
print(table.concat({"a", "b", "c"}, "-")) -- a-b-c
print(table.concat({1, 2, 3}, "")) -- 123테이블
테이블 생성
테이블은 배열과 사전으로 사용할 수 있습니다
-- named (global) function
function greet(name)
return "Hello, " .. name
end
-- local function (preferred)
local function add(a, b)
return a + b
end
print(greet("Lua")) -- Hello, Lua
print(add(2, 3)) -- 5
-- functions are first-class values
local f = add
print(f(10, 20)) -- 30요소 접근
두 가지 접근 방법: .와 []
local function minmax(arr)
local lo, hi = arr[1], arr[1]
for _, v in ipairs(arr) do
if v < lo then lo = v end
if v > hi then hi = v end
end
return lo, hi
end
local mn, mx = minmax({3, 1, 4, 1, 5})
print(mn, mx) -- 1 5
-- values are adjusted: extras dropped, missing become nil
local a = minmax({2, 7}) -- a = 2 (mx discarded)배열 연산
table 라이브러리는 배열 연산을 제공합니다
local function sum(...)
local total = 0
for _, v in ipairs({...}) do
total = total + v
end
return total
end
print(sum(1, 2, 3, 4)) -- 10
-- select('#', ...) = count; select(n, ...) = args from n
local function info(...)
print(select("#", ...)) -- number of args
print(select(2, ...)) -- args from position 2 onward
end정렬
사용자 정의 비교 함수 지원
-- functions are first-class: assign to a variable
local double = function(x) return x * 2 end
print(double(21)) -- 42
-- as table fields
local ops = {
add = function(a, b) return a + b end,
mul = function(a, b) return a * b end,
}
print(ops.add(2, 3), ops.mul(2, 3)) -- 5 6
-- passed inline to higher-order functions
table.sort({3, 1, 2}, function(a, b) return a > b end)반복
ipairs는 배열 부분을 반복합니다
local function map(arr, fn)
local result = {}
for i, v in ipairs(arr) do
result[i] = fn(v)
end
return result
end
local doubled = map({1, 2, 3}, function(x) return x * 2 end)
print(table.concat(doubled, ",")) -- 2,4,6
local function filter(arr, pred)
local out = {}
for _, v in ipairs(arr) do
if pred(v) then out[#out + 1] = v end
end
return out
end테이블 길이
#는 연속적인 배열에만 작동합니다
-- Lua has no native named args; pass a table instead
local function create(opts)
opts = opts or {}
local name = opts.name or "anonymous"
local age = opts.age or 0
return {name = name, age = age}
end
-- call f{...} is sugar for f({...})
local p = create{name = "Alice", age = 30}
print(p.name, p.age) -- Alice 30메타테이블
메타테이블 설정
__index는 조회 동작을 정의합니다
local function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local c = make_counter()
print(c()) -- 1
print(c()) -- 2
print(c()) -- 3
-- the inner function captures 'count' (an upvalue)연산자 오버로딩
+ - * / == < 같은 연산자를 오버로딩할 수 있습니다
local function make_account(balance)
return {
deposit = function(n) balance = balance + n end,
withdraw = function(n) balance = balance - n end,
get = function() return balance end,
}
end
local acc = make_account(100)
acc.deposit(50)
acc.withdraw(30)
print(acc.get()) -- 120
-- 'balance' is private: only the methods can touch it__index 상속
__index를 통한 상속
local function fibgen()
local a, b = 0, 1
return function()
a, b = b, a + b
return a
end
end
local next_fib = fibgen()
for i = 1, 8 do
io.write(next_fib(), " ") -- 1 1 2 3 5 8 13 21
end
print()__newindex
__newindex는 할당을 가로챕니다
local function memoize(fn)
local cache = {}
return function(x)
if cache[x] == nil then
cache[x] = fn(x)
end
return cache[x]
end
end
local function slow_square(n)
-- pretend this is expensive
return n * n
end
local fast_square = memoize(slow_square)
print(fast_square(5)) -- 25 (computed)
print(fast_square(5)) -- 25 (cached)__call
__call은 테이블을 함수처럼 호출할 수 있게 합니다
local function adder(x)
-- 'x' is an upvalue for the returned function
return function(y) return x + y end
end
local add5 = adder(5)
local add10 = adder(10)
print(add5(3)) -- 8
print(add10(3)) -- 13
-- each call to adder creates a separate upvalue
-- block scope with do-end
do
local tmp = "hidden"
end
-- print(tmp) -- error: tmp is out of scopeOOP
클래스 시뮬레이션
테이블과 메타테이블로 클래스를 시뮬레이션합니다
local t = {}
local mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt) -- true
-- setmetatable returns the table, so chain inline
local t2 = setmetatable({key = 1}, {
__tostring = function(self) return "MyTable" end,
})
print(tostring(t2)) -- MyTable객체 생성
: 호출은 자동으로 self를 전달합니다
local defaults = {color = "red", size = 10}
local obj = {color = "blue"}
setmetatable(obj, {__index = defaults})
print(obj.color) -- blue (own key wins)
print(obj.size) -- 10 (falls back to defaults)
print(obj.shape) -- nil (not found anywhere)
-- __index can also be a function: __index = function(t, k) ...상속
메타테이블 체인을 통한 상속
local log = {}
local t = setmetatable({}, {
__newindex = function(t, k, v)
log[k] = v -- record the assignment
rawset(t, k, v) -- actually store it (avoid recursion)
end,
})
t.x = 5 -- triggers __newindex
print(log.x, t.x) -- 5 5
-- rawset/rawget bypass metamethods메서드 재정의
하위 클래스 메서드가 상위를 재정의합니다
local Vec = {}
Vec.__index = Vec
Vec.__add = function(a, b) return Vec.new(a.x + b.x, a.y + b.y) end
Vec.__mul = function(a, n) return Vec.new(a.x * n, a.y * n) end
function Vec.new(x, y)
return setmetatable({x = x, y = y}, Vec)
end
local v1 = Vec.new(1, 2)
local v2 = Vec.new(3, 4)
local v3 = v1 + v2 -- uses __add
local v4 = v1 * 10 -- uses __mul
print(v3.x, v3.y) -- 4 6Comparison Metamethods
Comparison metamethods are __eq, __lt, and __le (Lua derives the others). They only apply when both operands share the same metamethod, and __eq requires both values to be of the same type.
local Money = {}
Money.__index = Money
Money.__lt = function(a, b) return a.cents < b.cents end
Money.__le = function(a, b) return a.cents <= b.cents end
Money.__eq = function(a, b) return a.cents == b.cents end
local function money(cents)
return setmetatable({cents = cents}, Money)
end
print(money(100) < money(200)) -- true
print(money(100) == money(100)) -- true__call & __tostring
__call makes a table callable as a function (great for functors and factories). __tostring controls how tostring() and print() render the table. Both dramatically improve the ergonomics of custom types.
local Greeter = {}
Greeter.__index = Greeter
Greeter.__call = function(self, who)
return "Hi " .. who .. ", I am " .. self.name
end
Greeter.__tostring = function(self)
return "Greeter(" .. self.name .. ")"
end
local g = setmetatable({name = "Bot"}, Greeter)
print(g("Alice")) -- Hi Alice, I am Bot
print(tostring(g)) -- Greeter(Bot)
-- __call lets a table be invoked like a function코루틴
코루틴 생성
coroutine.create는 코루틴을 생성합니다
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:speak()
return self.name .. " makes a sound"
end
local a = Animal.new("Cat")
print(a:speak()) -- Cat makes a sound코루틴 재개
resume은 코루틴을 시작하거나 재개합니다
local Counter = {}
Counter.__index = Counter
function Counter.new()
return setmetatable({count = 0}, Counter)
end
-- the colon ':' adds an implicit 'self' parameter
function Counter:inc()
self.count = self.count + 1
end
local c = Counter.new()
c:inc() -- sugar for Counter.inc(c)
c:inc()
print(c.count) -- 2코루틴 상태
코루틴은 네 가지 상태를 가집니다
local Point = {}
Point.__index = Point
function Point.new(x, y)
local obj = setmetatable({}, Point)
obj.x = x or 0
obj.y = y or 0
return obj
end
function Point:distance(other)
local dx, dy = self.x - other.x, self.y - other.y
return math.sqrt(dx*dx + dy*dy)
end
local p1 = Point.new(0, 0)
local p2 = Point.new(3, 4)
print(p1:distance(p2)) -- 5yield 값 전달
yield와 resume은 양방향으로 값을 전달할 수 있습니다
local Animal = {}
Animal.__index = Animal
function Animal.new(name) return setmetatable({name = name}, Animal) end
function Animal:speak() return self.name .. " speaks" end
local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog
function Dog.new(name)
return setmetatable(Animal.new(name), Dog)
end
function Dog:bark() return self.name .. " barks!" end
local d = Dog.new("Rex")
print(d:speak()) -- Rex speaks (inherited from Animal)
print(d:bark()) -- Rex barks! (Dog's own method)코루틴 반복자
coroutine.wrap은 호출 가능한 함수를 반환합니다
local Shape = {}
Shape.__index = Shape
function Shape.new() return setmetatable({}, Shape) end
function Shape:area() return 0 end
local Circle = setmetatable({}, {__index = Shape})
Circle.__index = Circle
function Circle.new(r) return setmetatable({r = r}, Circle) end
function Circle:area() return math.pi * self.r * self.r end -- override
local c = Circle.new(2)
print(c:area()) -- 12.566...
-- call the parent method explicitly: Shape.area(c)Class-based Inheritance Pattern
A small Class() helper standardizes creation and inheritance: new() builds an instance and calls init if present. This pattern is the basis of many Lua OOP libraries (middleclass, classic, etc.).
local function Class(base)
local cls = {}
cls.__index = cls
if base then setmetatable(cls, {__index = base}) end
cls.new = function(...)
local obj = setmetatable({}, cls)
if obj.init then obj:init(...) end
return obj
end
return cls
end
local Vehicle = Class()
function Vehicle:init(wheels) self.wheels = wheels end
function Vehicle:desc() return self.wheels .. " wheels" end
local Bike = Class(Vehicle)
function Bike:init() Vehicle.init(self, 2) end
print(Bike.new():desc()) -- 2 wheels모듈
모듈 정의
모듈 기능을 포함하는 테이블을 반환합니다
local co = coroutine.create(function(a, b)
print("start", a, b)
local c = coroutine.yield(a + b)
print("resumed with", c)
return "done"
end)
-- coroutine.create returns a thread
print(type(co)) -- thread
print(coroutine.status(co)) -- suspended모듈 사용
require는 모듈을 한 번만 로드합니다
local co = coroutine.create(function()
for i = 1, 3 do
coroutine.yield(i)
end
end)
print(coroutine.resume(co)) -- true 1
print(coroutine.resume(co)) -- true 2
print(coroutine.resume(co)) -- true 3
print(coroutine.resume(co)) -- true (no more values)
print(coroutine.status(co)) -- dead모듈 경로
?.lua에서 ?는 모듈 이름으로 대체됩니다
-- states: suspended, running, normal, dead
local co = coroutine.create(function()
coroutine.yield()
end)
print(coroutine.status(co)) -- suspended
coroutine.resume(co)
print(coroutine.status(co)) -- suspended (yielded)
coroutine.resume(co)
print(coroutine.status(co)) -- dead
-- coroutine.running() returns the current coroutine (nil in main)
print(coroutine.running()) -- nil (main thread)재로딩
캐시를 지운 후 다시 require하세요
local co = coroutine.create(function(x)
local y = coroutine.yield(x * 2) -- sends x*2, receives y
local z = coroutine.yield(y * 3) -- sends y*3, receives z
return z
end)
print(coroutine.resume(co, 10)) -- true 20 (x=10, yields 20)
print(coroutine.resume(co, 5)) -- true 15 (y=5, yields 15)
print(coroutine.resume(co, 7)) -- true 7 (z=7, returns 7)coroutine.wrap
coroutine.wrap returns a plain function that resumes the coroutine each call—more convenient than create+resume and perfect for iterators. The trade-off: errors raise instead of returning (ok, err), so there's no built-in error flag.
-- wrap returns a function (not a thread); it auto-resumes
-- and propagates errors instead of returning ok
local gen = coroutine.wrap(function()
for i = 1, 5 do
coroutine.yield(i * i)
end
end)
print(gen()) -- 1
print(gen()) -- 4
print(gen()) -- 9
-- ideal for iterator-style use
for v in coroutine.wrap(function()
coroutine.yield("a"); coroutine.yield("b")
end) do
print(v) -- a, then b
break
end오류 처리
error 던지기
error는 예외를 던집니다
-- file: mymath.lua
local M = {}
function M.square(n) return n * n end
function M.cube(n) return n * n * n end
M.pi = 3.14159
return M
-- a module is just a table returned by the chunkpcall 보호 호출
pcall은 오류를 잡습니다
-- require loads the module and caches it in package.loaded
local mymath = require("mymath")
print(mymath.square(5)) -- 25
print(mymath.cube(3)) -- 27
print(mymath.pi) -- 3.14159
-- subsequent requires return the cached table
local m2 = require("mymath")
print(m2 == mymath) -- true
-- dotted names map to paths: a.b.c -> a/b/c.luaxpcall과 traceback
xpcall은 오류 처리기를 지정할 수 있게 합니다
-- package.path controls where require looks for Lua modules
print(package.path)
-- patterns like ./?.lua;./?/init.lua;? is the module name
-- add a custom directory
package.path = "./libs/?.lua;" .. package.path
-- require("foo") then tries ./libs/foo.lua
-- package.cpath is the equivalent for compiled C modules
-- (?.so on Unix, ?.dll on Windows)assert
assert는 조건이 거짓일 때 던집니다
-- require caches in package.loaded; clear it to reload
package.loaded["mymath"] = nil
local mymath = require("mymath") -- re-runs the chunk
-- a hot-reload helper
local function reload(name)
package.loaded[name] = nil
return require(name)
end
-- warning: existing references to the old table
-- are NOT updated after a reloaderror 레벨
두 번째 매개변수가 오류 위치를 제어합니다
-- pattern 1: return a table (most common)
local M = {}
function M.fn() end
return M
-- pattern 2: keep helpers local, export a clean table
local function helper() end
local function main_fn() helper() end
return { main = main_fn }
-- pattern 3: module() (legacy, Lua 5.1) -- deprecated
-- module("foo"); function _M.fn() end문자열 조작
기본 연산
string 라이브러리는 문자열 연산을 제공합니다
local function divide(a, b)
if b == 0 then
error("division by zero") -- raises an error
end
return a / b
end
-- the level argument controls the reported position
error("msg", 0) -- no position info added
error("msg", 1) -- points to the caller of error (default)
error("msg", 2) -- points to the caller's caller
-- error() can throw non-string objects too
error({code = 42, msg = "x"})부분 문자열
인덱스는 1부터 시작, 음수 지원
local function risky(x)
if x < 0 then error("negative") end
return math.sqrt(x)
end
local ok, result = pcall(risky, -1)
if ok then
print("result:", result)
else
print("error:", result) -- error: negative
end
local ok2, r2 = pcall(risky, 16)
print(ok2, r2) -- true 4찾기와 교체
find는 시작과 끝 위치를 반환합니다
local function handler(err)
return debug.traceback("Error: " .. tostring(err), 2)
end
local function f() error("boom") end
-- xpcall takes a handler; Lua 5.3+ also accepts arguments
local ok, err = xpcall(f, handler)
print(ok, err) -- false Error: boom\nstack traceback:...
-- xpcall with arguments (Lua 5.3+)
local function risky(x) if x < 0 then error("x") end return x end
local ok2, r2 = xpcall(risky, handler, 9)
print(ok2, r2) -- true 9서식
C printf와 유사합니다
-- assert(v, msg) raises if v is nil or false
local function load_config(path)
local f = assert(io.open(path, "r")) -- raises if file missing
return f:read("*a")
end
local n = assert(tonumber("42"), "not a number") -- 42
-- assert(nil, "bad input") -- raises: bad input
-- common idiom: assert on results that return nil on failure
local line = assert(f:read("*L"))문자와 ASCII
char와 byte는 서로 변환합니다
local function check(x)
if type(x) ~= "number" then
-- level 2 blames the caller of check, not check itself
error("expected number, got " .. type(x), 2)
end
return x * 2
end
-- level 0: add no position information
-- level 1: point at the error() call (default)
-- level 2: point at who called the function containing error()수학 함수
기본 수학
math 라이브러리는 수학 함수를 제공합니다
-- the simple model uses default input/output streams
io.write("Enter your name: ")
local name = io.read() -- read a line
print("Hi " .. name)
-- io.read modes:
-- "*n" a number
-- "*a" the entire remaining input
-- "*l" a line (default, no newline)
-- "*L" a line including the newline
local n = io.read("*n")
local all = io.read("*a")삼각 함수
매개변수는 라디안입니다
io.output("out.txt") -- set the default output file
io.write("line one\n")
io.write("line two\n")
io.close() -- close the default output
-- append mode needs io.open
local f = io.open("out.txt", "a")
f:write("appended\n")
f:close()로그와 거듭제곱
log의 두 번째 매개변수는 밑입니다
-- io.open returns a file handle (or nil + error message)
local f, err = io.open("data.txt", "r")
if not f then
error("could not open: " .. err)
end
local content = f:read("*a") -- read the entire file
print(content)
f:close() -- always close handles
-- handle methods: f:read, f:write, f:lines, f:seek, f:close난수
먼저 난수 시드를 설정하세요
local f = assert(io.open("data.txt", "r"))
for line in f:lines() do
print(line)
end
f:close() -- f:lines does NOT close the handle
-- io.lines(path) opens, iterates, and closes automatically
for line in io.lines("data.txt") do
print(line)
end반올림과 부호
modf는 정수 부분과 소수 부분을 반환합니다
-- io.open(path, mode) modes:
-- "r" read (default); file must exist
-- "w" write; truncate or create
-- "a" append; create if missing
-- "r+" read/write; file must exist
-- "w+" read/write; truncate or create
-- "a+" read/append; create if missing
-- append "b" for binary on some systems: "rb", "wb"
local f = io.open("log.txt", "a")
f:write(os.date(), " started\n")
f:close()File Positioning
f:seek moves the read/write position. 'set' is absolute from the start, 'cur' is relative to the current position, 'end' is from the end. seek('end') with no offset returns the file size.
local f = assert(io.open("data.txt", "r"))
-- f:seek(whence, offset)
-- "set": from the start, "cur": from current, "end": from end
print(f:seek("end")) -- file size (position at end)
f:seek("set", 0) -- rewind to the start
local first_line = f:read("*l")
print(first_line)
f:close()파일 I/O
간단한 I/O
io.read 모드 매개변수
-- Lua patterns are NOT regular expressions; they use % not \
local s = "hello world"
print(s:find("wor")) -- 7 9 (start and end indices)
print(s:find("xyz")) -- nil
-- . matches any single character
print(("abc"):match(".")) -- a
-- string.find / gmatch / gsub / match all use patterns
print(("x = 42"):match("%d+")) -- 42파일 쓰기
io.output은 출력 파일을 설정합니다
-- %a letters, %d digits, %s whitespace, %w alnum
-- %p punctuation, %l lower, %u upper, %c control
-- uppercase = negation: %A non-letters, %D non-digits
print(("a1 b2"):match("%a%d")) -- a1
print(("x = 42"):match("%d+")) -- 42
-- custom sets: [abc], [a-z], [^0-9]
print(("a-b"):match("[a-z]")) -- a
print(("phone 555-1234"):match("%d+-%d+")) -- 555-1234파일 핸들
io.open은 파일 핸들을 반환합니다
-- parentheses () create captures
local s = "name=Alice age=30"
local k, v = s:match("(%w+)=(%w+)")
print(k, v) -- name Alice
-- gmatch iterates over all matches
for key, val in ("a=1 b=2 c=3"):gmatch("(%w+)=(%w+)") do
print(key, val) -- a 1 / b 2 / c 3
end
-- an empty capture () returns the current position
print(("abc"):match("()b")) -- 2줄 단위 읽기
f:lines()는 반복자를 반환합니다
local s = "hello world"
print(s:gsub("o", "0")) -- hell0 w0rld 2 (string, count)
-- replacements can reference captures %1, %2
print(("2024-01-15"):gsub("(%d+)-(%d+)-(%d+)", "%3/%2/%1")) -- 15/01/2024 1
-- a function receives captures and returns the replacement
print(("1 2 3"):gsub("%d", function(d) return d * 2 end)) -- 2 4 6 3
-- a 4th argument limits the number of replacements
print(("aaaa"):gsub("a", "b", 2)) -- bbaa 2파일 모드
파일 열기 모드
-- ^ anchors to the start, $ anchors to the end
print(("hello"):match("^he")) -- he
print(("hello"):match("lo$")) -- lo
print(("hello"):match("^hello$")) -- hello
-- pattern items:
-- * zero or more (greedy)
-- + one or more (greedy)
-- - zero or more (lazy)
-- ? zero or one
print(("aXXXb"):match("a(%a+)b")) -- XXX (greedy)
print(("<!--c-->"):match("<!--(.-)-->")) -- c (lazy)날짜/시간
현재 시간
os.date("*t")는 테이블을 반환합니다
local s = "Lua"
print(s:upper(), s:lower(), s:len()) -- LUA lua 3
print(s:rep(3)) -- LuaLuaLua
print(s:reverse()) -- auL
print(s:sub(1, 2)) -- Lu
print(s:byte(1), string.char(76)) -- 76 L
print(string.format("%s %d", "v", 5)) -- Lua v 5
-- full set: find, gmatch, gsub, match, format,
-- rep, sub, byte, char, len, upper, lower, pack, unpack서식화된 시간
strftime과 유사한 서식
local t = {1, 2, 3}
table.insert(t, 4) -- {1,2,3,4}
table.insert(t, 1, 0) -- {0,1,2,3,4}
table.remove(t, 1) -- removes 0, returns it
table.sort(t) -- sort in place
print(table.concat(t, ",")) -- 1,2,3,4
-- Lua 5.2+: table.pack / table.unpack
local args = table.pack(1, 2, 3) -- {1,2,3, n=3}
print(table.unpack(args)) -- 1 2 3
-- Lua 5.3+: table.move(src, f, e, t[, dst])시간 테이블
시간 테이블을 타임스탬프로 변환
print(math.pi, math.huge) -- 3.1415926535898 inf
print(math.max(1, 5, 3)) -- 5
print(math.floor(3.7), math.ceil(3.2)) -- 3 4
print(math.abs(-5)) -- 5
print(math.sqrt(16)) -- 4.0
print(math.sin(math.pi / 2)) -- 1.0 (radians)
print(math.random(1, 100)) -- random int in [1,100]
math.randomseed(os.time()) -- seed the generator
print(math.type(3), math.type(3.0)) -- integer float시간 차이
os.difftime은 시간 차이를 계산합니다
-- default input/output streams
io.write("to stdout\n")
local line = io.read("*l") -- read a line
-- file handles
local f = io.open("x.txt", "w")
f:write("data\n")
f:close()
-- io.stdin, io.stdout, io.stderr are handles
io.stderr:write("an error\n")
-- io.tmpfile() returns a handle to an auto-deleted temp file
local tmp = io.tmpfile()
tmp:write("scratch")
tmp:seek("set", 0)clock
os.clock은 CPU 시간을 반환합니다
print(os.time()) -- current timestamp (seconds)
print(os.date("%Y-%m-%d %H:%M")) -- formatted date
local t = os.date("*t") -- table: year, month, day, hour...
print(t.year, t.month, t.day)
print(os.getenv("PATH")) -- an environment variable (or nil)
os.execute("ls") -- run a shell command
local h = io.popen("date") -- capture command output
print(h:read("*a"))
h:close()Debug library
The debug library offers introspection: traceback for stacks, getinfo for function/frame metadata, getlocal/setlocal for variables, and setmetatable/getmetatable that bypass metamethod restrictions. Use sparingly—it can break invariants.
-- debug.traceback returns a call-stack string
local function f() error("x") end
print(xpcall(f, function(e) return debug.traceback(e) end))
-- debug.getinfo returns info about a function or stack frame
local info = debug.getinfo(print)
print(info.what, info.name) -- C print
-- debug.getlocal / setlocal inspect stack locals
local function locals()
local a = 1
local name, value = debug.getlocal(1, 1)
print(name, value) -- a 1
end
locals()패턴 매칭
기본 패턴
Lua 패턴은 정규 표현식이 아닙니다
// Lua is embedded via a C library; the core object is lua_State
// Headers: lua.h, lauxlib.h, lualib.h
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
int main(void) {
lua_State *L = luaL_newstate(); // create a state
luaL_openlibs(L); // open the standard libs
luaL_dostring(L, "print('hi')"); // run Lua source
lua_close(L); // free the state
return 0;
}문자 클래스
Lua 패턴 문자 클래스
// all communication with Lua goes through a stack
lua_State *L = luaL_newstate();
// push values
lua_pushinteger(L, 42);
lua_pushstring(L, "hello");
// stack now: bottom[42]["hello"]top
// inspect (1-based from bottom, negative from top)
int top = lua_gettop(L); // 2
lua_Integer n = lua_tointeger(L, 1); // 42
const char *s = lua_tostring(L, 2); // "hello"
lua_pop(L, 2); // pop 2 values캡처
()는 매치된 내용을 캡처합니다
// script.lua: function add(a, b) return a + b end
luaL_dofile(L, "script.lua");
lua_getglobal(L, "add"); // push the function 'add'
lua_pushinteger(L, 3); // arg 1
lua_pushinteger(L, 4); // arg 2
// lua_pcall(L, nargs, nresults, msgh)
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
} else {
lua_Integer sum = lua_tointeger(L, -1); // 7
lua_pop(L, 1); // pop the result
}gsub 교체
교체물로 함수를 사용할 수 있습니다
// a Lua-callable C function has signature: int fn(lua_State *L)
static int l_square(lua_State *L) {
lua_Integer n = luaL_checkinteger(L, 1); // read arg 1, type-checked
lua_pushinteger(L, n * n); // push the result
return 1; // number of results
}
// register it so Lua can call it
lua_pushcfunction(L, l_square);
lua_setglobal(L, "square");
// now in Lua: print(square(5)) -> 25앵커
^는 시작, $는 끝
// register a table of C functions as a Lua module
static const luaL_Reg mylib[] = {
{"square", l_square},
{"cube", l_cube},
{NULL, NULL} // sentinel marks the end
};
int luaopen_mylib(lua_State *L) {
luaL_newlib(L, mylib); // create a table and register the funcs
return 1; // return that table (the module)
}
// in Lua: local mylib = require("mylib"); print(mylib.square(5))
// compiled as a shared lib (.so/.dll) found via package.cpath클로저
클로저 기본
클로저는 외부 변수를 캡처합니다
-- LuaJIT is a Just-In-Time compiler for Lua 5.1, much faster
-- detect LuaJIT at runtime
if jit then
print(jit.version) -- e.g. LuaJIT 2.1.0-beta3
print(jit.status()) -- true if JIT compilation is on
print(jit.os, jit.arch) -- e.g. Linux x64
end
-- 'jit' is nil on standard PUC Lua, so this guard is safe
if not jit then
print("running plain Lua")
end상태 캡슐화
클로저는 private 상태를 구현합니다
jit.on() -- enable JIT compilation globally
jit.off() -- disable it (fall back to the interpreter)
jit.flush() -- flush the compiled-code cache
-- control compilation per-function
local function hot()
local s = 0
for i = 1, 1e6 do s = s + i end
return s
end
jit.off(hot) -- never compile this function
-- jit.on(hot) to re-enable
-- useful for debugging or working around compiler bugs제네레이터
클로저로 제네레이터 패턴을 구현합니다
local ffi = require("ffi")
-- ffi.C is the default C namespace (libc on most systems)
-- ffi.load("name") loads a shared library explicitly
local C = ffi.C
local libc = ffi.load("c")
-- many standard C functions are available right away
print(ffi.C.time(nil)) -- current Unix timestamp
-- ffi.load("m") for libm, ffi.load("ssl") for OpenSSL, etc.FFI: Type Declarations (cdef)
ffi.cdef declares C types, structs, and function prototypes (similar to a header). ffi.new allocates a C object; ffi.C (or a loaded library) calls the declared functions. C structs are usable as Lua values with near-C speed.
local ffi = require("ffi")
-- declare C types and prototypes with ffi.cdef
ffi.cdef[[
typedef struct { int x, y; } Point;
int printf(const char *fmt, ...);
]]
-- create and use a C struct directly
local p = ffi.new("Point", {x = 1, y = 2})
print(p.x, p.y) -- 1 2
ffi.C.printf("p = (%d, %d)\n", p.x, p.y) -- calls libc printfFFI: Calling C Functions
After cdef declares a prototype, ffi.C.name calls the C function. C numbers map to Lua numbers; C strings (char*) must be converted with ffi.string to become Lua strings. Under the JIT these calls have minimal overhead.
local ffi = require("ffi")
ffi.cdef[[
double sqrt(double x);
int abs(int x);
const char *getenv(const char *name);
]]
-- call C's math functions directly (very fast under JIT)
local root = ffi.C.sqrt(16.0) -- 4.0
local pos = ffi.C.abs(-7) -- 7
print(root, pos)
-- ffi.string converts a C string (const char *) to a Lua string
local path = ffi.string(ffi.C.getenv("PATH"))
print(#path)FFI: Callbacks from C to Lua
ffi.cast turns a Lua function into a C function pointer usable as a callback. C arrays are zero-indexed. Callbacks are expensive and must be freed with :free() when no longer needed (they pin Lua objects).
local ffi = require("ffi")
ffi.cdef[[
typedef int (*cmp_fn)(const void *, const void *);
void qsort(void *base, size_t n, size_t sz, cmp_fn cmp);
]]
-- create a C callback that calls back into Lua
local arr = ffi.new("int[?]", 5, {5, 3, 1, 4, 2})
local cmp = ffi.cast("cmp_fn", function(a, b)
return ffi.cast("int*", a)[0] - ffi.cast("int*", b)[0]
end)
ffi.C.qsort(arr, 5, ffi.sizeof("int"), cmp)
for i = 0, 4 do io.write(arr[i], " ") end -- 1 2 3 4 5
cmp:free() -- free the callback when done스코프
지역 스코프
do-end는 블록 스코프를 생성합니다
-- Redis runs Lua scripts via EVAL; keys are KEYS, args are ARGV
-- the script returns a single value to Redis
-- run in redis-cli:
-- EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey hello
local key = KEYS[1]
local val = ARGV[1]
redis.call('SET', key, val)
return redis.call('GET', key) -- "hello"
-- redis.call: errors abort the script
-- redis.pcall: errors are returned as a table클로저 캡처
클로저는 변수 참조를 유지합니다
-- KEYS[1..N] = keys passed to EVAL (N is EVAL's 2nd argument)
-- ARGV[1..M] = additional arguments
-- EVAL "..." numkeys key1 key2... arg1 arg2...
-- example: sum the values at multiple keys
local total = 0
for i = 1, #KEYS do
local v = tonumber(redis.call('GET', KEYS[i]))
if v then total = total + v end
end
return total
-- use SCRIPT LOAD + EVALSHA to cache and reuse scripts루프 변수
Lua에서 루프 변수는 매 반복마다 새롭습니다
-- OpenResty embeds LuaJIT in Nginx via ngx_http_lua_module
-- content_by_lua_block runs Lua to generate the response
-- nginx.conf:
-- location /hello {
-- content_by_lua_block {
-- ngx.say("hello from lua")
-- }
-- }
-- ngx.say writes to the response body (with a newline)
-- ngx.print writes without a newline
ngx.status = 200
ngx.say("Hello, ", ngx.var.arg_name)upvalue
클로저가 참조하는 외부 변수를 upvalue라고 합니다
-- ngx.var.* accesses Nginx variables
local uri = ngx.var.uri
local ua = ngx.var.http_user_agent
-- ngx.req: the request API
ngx.req.read_body()
local body = ngx.req.get_body_data()
-- request headers (table, case-insensitive keys)
local h = ngx.req.get_headers()
print(h["Content-Type"])
-- set response headers, then write the body
ngx.header["X-Custom"] = "yes"
ngx.say("uri=", uri)ngx.location.capture
ngx.location.capture issues a non-blocking subrequest to another Nginx location and returns status, header, and body. capture_multi runs several in parallel. Subrequests don't go to the client—they're an internal composition tool.
-- issue subrequests to other Nginx locations
local res = ngx.location.capture("/api/users")
if res.status == 200 then
ngx.say(res.body)
end
-- capture_multi: parallel subrequests
local res1, res2 = ngx.location.capture_multi{
{"/api/a"},
{"/api/b"},
}
ngx.say(res1.body, res2.body)
-- subrequests are internal; great for composing upstream servicesShared Dict (ngx.shared)
Shared dicts (lua_shared_dict) are thread-safe, in-memory stores shared across Nginx worker processes. They support atomic ops like incr and add, making them ideal for rate limiting, caching, and locks in OpenResty apps.
-- in nginx.conf: lua_shared_dict cache 10m;
-- access a dict shared across worker processes
local cache = ngx.shared.cache
cache:set("user:1", "Alice", 60) -- key, value, ttl in seconds
local name = cache:get("user:1") -- "Alice"
cache:incr("hits", 1, 0) -- atomic increment, init 0
cache:delete("old")
-- safe for concurrent access across Nginx workers
-- common uses: rate limiting, caching, distributed locks
local locked = cache:add("lock:job", 1, 30) -- set only if absent관련 Lua 스니펫
Copy-paste ready code for common tasks.
Was this helpful?