언어 — 표현식
언어 — 표현식 (The Language — Expressions)
루아의 표현식(expression)은 값들을 계산해요. 기본 표현식은 다음과 같아요.
exp ::= nil | false | true | Numeral | LiteralString
| functiondef | prefixexp | tableconstructor | '...'
| exp binop exp | unop exp
즉 표현식은 리터럴 상수, 함수 정의, 접두사 표현식(= 변수·함수 호출·테이블 접근), 테이블 생성자, 가변 인자(...), 이항·단항 연산의 조합이에요.
산술 연산자 (Arithmetic Operators)
이항 산술 연산자는 +(덧셈), -(뺄셈), *(곱셈), /(나눗셈), //(바닥 나눗셈), %(나머지), ^(거듭제곱), 단항 -(부정)이에요.
x = 10 + 5 --> 15
y = 7 // 2 --> 3 (바닥 나눗셈, 정수)
r = 7 % 3 --> 1 (나머지)
p = 2 ^ 10 --> 1024
/는 항상 실수 나눗셈(부동소수점)을 수행해요.//는 피연산자 중 하나가 실수면 실수로, 둘 다 정수면 정수 바닥 나눗셈이에요.%는 나머지 연산으로,a % b == a - floor(a/b) * b와 같아요. 두 피연산자가 정수면 정수 나머지, 그 외엔 실수로 동작해요.^는 거듭제곱. 우결합(right associative)이라2^3^2는2^(3^2)로 해석돼요.- 단항
-는 부호 반전이에요.
산술 연산은 기본적으로 숫자 피연산자에 적용돼요. 숫자가 아니면 __add, __sub 등의 메타메서드를 시도해요 (2.4 절 참조).
비트 연산자 (Bitwise Operators)
비트 연산자는 &(AND), |(OR), ~(XOR), ~(단항 NOT), <<(왼쪽 시프트), >>(오른쪽 시프트)예요.
a = 0b1100 -- 12
b = 0b1010 -- 10
print(a & b) --> 8 (0b1000)
print(a | b) --> 14 (0b1110)
print(a ~ b) --> 6 (0b0110)
print(~a) --> -13 (비트 NOT, 부호 포함)
print(1 << 4) --> 16
print(16 >> 2) --> 4
비트 연산은 정수 피연산자를 요구해요. 실수(정수 값)가 들어오면 정수로 변환되고, 변환 불가능하면 오류가 발생해요. 음수 시프트 횟수나 범위를 벗어나는 시프트는 0이 돼요. 비트 연산에도 __band, __bor, __bxor, __bnot, __shl, __shr 메타메서드가 있어요.
강제 변환과 변환 (Coercions and Conversions)
루아는 특정 상황에서 자동으로 값을 변환(강제 변환, coercion)해요.
- 문자열 → 숫자: 산술 연산에서 문자열 피연산자에 "숫자와 일치하는" 텍스트가 있으면(예:
"10","3.5","0xff") 자동으로 숫자로 변환돼요."10" + 1은11이 돼요. 변환 불가능한 문자열이면 오류가 발생해요.tonumber를 사용하면 명시적으로 변환할 수 있고,"10" == 10은 거짓이지만"10" + 1은 가능해요. - 정수 ⇄ 실수: 산술 연산에서 정수와 실수가 섞이면 실수로 승격돼요.
1 + 0.5는1.5예요.tonumber,math.type등으로 구분할 수 있어요. - 숫자 → 문자열: 문자열 연결(
..)에서 숫자는 문자열로 변환돼요.10 .. "개"는"10개"가 돼요.tostring으로 명시적으로 변환할 수도 있어요.
루아에서 문자열 연결 연산자 ..는 숫자를 자동으로 문자열로 변환하지만, 산술 연산은 문자열을 자동으로 숫자로 변환해요. 다만, #(길이) 연산자는 문자열에만 적용되고, == 비교는 타입이 다른 두 값(숫자와 문자열)을 같다고 판단하지 않아요.
관계 연산자 (Relational Operators)
관계 연산자는 ==(등호), ~=(부등호), <(작음), >(큼), <=(작거나 같음), >=(크거나 같음)이에요.
print(1 == 1) --> true
print(1 ~= 2) --> true
print("a" < "b") --> true (사전식 비교)
==와~=는 다른 타입의 두 값을 비교할 때 (테이블/함수 등__eq가 없는 경우) 그 값이 같지 않다고 판단해요. 즉1 == "1"은false예요. 같은 타입이면nil은 자기 자신과 같고, 불변 값(숫자·문자열)은 값으로, 가변 값(테이블 등)은 참조(identity)로 비교해요.<,>,<=,>=는 숫자와 문자열에 적용되고, 둘 다 숫자거나 둘 다 문자열이어야 해요. 사전식 문자열 비교는 바이트 단위로 이뤄져요. 다른 타입이면__lt,__le메타메서드를 시도해요.
논리 연산자 (Logical Operators)
논리 연산자는 and, or, not이에요.
local a = true and 10 -- a는 10 (true and 값)
local b = nil or "기본값" -- b는 "기본값"
local c = not nil -- c는 true
not은 항상 boolean을 반환해요.and와or는 단락(short-circuit) 평가를 해서, 첫 번째 피연산자만으로 결과가 정해지면 두 번째는 평가하지 않아요.a and b는a가 거짓이면a를, 아니면b를 반환해요.a or b는a가 참이면a를, 아니면b를 반환해요.nil과false만 거짓이고 나머지는 참이라는 점에서,and/or는 값 자체를 반환하므로a or fallback같은 관용구(패턴)가 널리 쓰여요.
연결 연산자 (Concatenation)
..는 문자열 연결 연산자예요. 피연산자 중 하나가 숫자면 문자열로 변환된 뒤 연결돼요.
print("루아" .. " 5.4") --> 루아 5.4
print(10 .. 2) --> 102
print(1 .. 2 .. 3) --> 123 (좌결합)
..는 좌결합(left associative)이에요. 피연산자에 __concat 메타메서드가 있으면 그것이 호출돼요.
길이 연산자 (The Length Operator)
#는 길이 연산자예요.
- 문자열에 적용하면 그 바이트 수(길이)를 반환해요. UTF-8 문자열의 "문자 수"가 아니라 바이트 수라는 점에 주의하세요.
- 테이블에 적용하면 "테이블의 길이"를 반환하는데, 루아는
#t를 최대 인덱스라고 정의하지 않고 "배열" 부분의 경계로 정의해요. 구체적으로 정수 키 중 연속된 1, 2, ... 중 어디까지 실제 값이 있는지에 따라 결정되는, 경계가 될 수 있는 정수n을 반환해요. 즉#t는t[n] ~= nil이고t[n+1] == nil인 어떤n을 주기 때문에, 희소 배열(sparse array)이나 정수 키가 구멍(hole) 있는 테이블에서는 "정의되지 않은 동작"이 아니라 "경계가 불명확"해서 아무 값일 수 있어요. 배열(1부터 연속) 테이블에서는 항상 원소 수가 돼요. - 그 외 값에 적용하면
__len메타메서드를 시도하고, 없으면 오류가 발생해요.
우선순위 (Precedence)
루아의 연산자 우선순위는 (높은 것부터), 괄호로 묶인 표현식이 가장 높아요:
or (가장 낮음)
and
< > <= >= ~= ==
|
~
&
<< >>
.. (우결합)
+ -
* / // %
unary operators (not # - ~)
^ (우결합, 단항 연산자보다 높음)
..와^는 우결합(right associative)이에요.^는 단항 연산자보다 우선순위가 높아서-x^2는-(x^2)로 해석돼요.- 다른 이항 연산자는 모두 좌결합(left associative)이에요.
- 괄호
( ... )로 그룹화할 수 있고, 우선순위를 명시적으로 제어할 수 있어요.
테이블 생성자 (Table Constructors)
테이블 생성자는 테이블 값을 만드는 표현식이에요. {}로 빈 테이블을 만들고, 필드를 나열해 초기화할 수 있어요. 필드는 세 가지 형태가 있어요.
local t = {} -- 빈 테이블
local t2 = { x = 10, y = 20 } -- 레코드 스타일 (키: 값)
local t3 = { "a", "b", "c" } -- 배열 스타일 (1, 2, 3 키)
local t4 = { [1] = "일", [3] = "삼" } -- 명시적 키
- 레코드 스타일:
{ name = value, ... }— 키가 문자열. - 배열 스타일:
{ value, value, ... }— 키가 자동으로 1, 2, 3... - 명시적 키:
{ [exp] = exp }— 어떤 표현식이든 키가 될 수 있음.
생성자에서 필드 뒤에 콤마(,)나 세미콜론(;)을 쓸 수 있고, 마지막 필드 뒤에는 생략해도 돼요. 배열 스타일과 레코드 스타일은 섞을 수 있어요.
local t = {
"첫 번째", -- t[1]
name = "루아", -- t["name"]
[10] = "열 번째" -- t[10]
}
생성자에서 필드 값으로 함수를 바로 정의하는 것도 흔해요.
local obj = {
value = 0,
add = function(self, n) self.value = self.value + n end
}
생성자에서 __index = self 같은 고급 패턴으로 메타테이블을 설정할 수도 있어요.
함수 호출 (Function Calls)
함수 호출은 prefixexp(args) 형태이고, 메서드 호출은 콜론(:)을 써요. 호출은 __call 메타메서드가 있으면 테이블에서도 가능해요.
f(1, 2) -- 함수 호출
obj:method(3) -- 메서드 호출 (obj.method(obj, 3)과 같음)
t["fn"](4) -- 필드 함수 호출
인자를 몇 개 넣든 호출할 수 있고, 반환값은 호출 위치에 따라 하나 또는 여러 개로 조정돼요. 함수 호출이 문장의 마지막 표현식이나 return의 마지막 표현식이면 모든 반환값이 전달돼요. 그 외 위치에서는 첫 번째 값만 쓰여요 (3.4.12 "표현식 목록" 참조).
함수 정의 (Function Definitions)
함수 정의는 function 키워드로 만들고, functiondef 표현식 또는 function ... end 문장 형태예요.
local function add(a, b) -- 지역 함수 정의
return a + b
end
function greet(name)
return "안녕, " .. name
end
-- 익명 함수 (값으로)
local double = function(x) return x * 2 end
- 함수 본문은 블록이고,
local변수를 만들 수 있고,return값은 여러 개일 수 있어요. - 함수는 다른 함수 안에서 정의될 수 있고, 그 경우 바깥 함수의 지역 변수(업밸류, upvalue)를 포획(clos체ure)해요. 이런 함수를 클로저(closure) 라고 해요.
- 매개변수는 함수 지역 변수로 취급돼요. 가변 인자(vararg) 함수는
...로 받아요. - 메서드 정의
function t:f(...) ... end는 자동으로self라는 첫 번째 파라미터를 추가해요.
local function counter()
local n = 0
return function()
n = n + 1
return n
end
end
local c = counter()
print(c()) --> 1
print(c()) --> 2
표현식 목록, 다중 결과, 조정 (Lists of expressions, multiple results, and adjustment)
루아의 함수 호출은 여러 값을 반환할 수 있어요. 표현식 목록(explist)에서 함수 호출이 목록의 마지막 표현식이면 그 모든 반환값이 사용돼요. 그 외 위치에서는 첫 번째 값만 사용돼요.
local a, b = f() -- f의 반환값이 a, b로 들어감
local x = f() -- f의 첫 반환값만 x로
할당, return, 함수 인자, 테이블 생성자 등에서 조정(adjustment) 이 일어나요.
- 할당에서 값이 변수 수보다 많으면 잘리고, 적으면
nil로 채워져요. - 함수 호출 인자에서, 마지막 인자가 함수 호출이면 그 반환값들이 인자로 펼쳐져요.
return은 함수의 반환값이 조정돼요 — return이 여러 값이면 호출자가 받아요.
function f() return 1, 2, 3 end
local a, b, c = f() -- a=1, b=2, c=3
print(f()) --> 1 2 3
local d = f() -- d=1 (첫 값만)
...(vararg)도 여러 값을 표현하는데, select("#", ...)로 개수를, {...}로 테이블로 묶을 수 있어요.
본문
이항·단항 연산자 표
모든 이항 연산자는 exp binop exp 형태, 단항 연산자는 unop exp 형태예요. 단항 연산자는 not, #(길이), -(부정), ~(비트 NOT)이에요. 이항 연산자와 우선순위는 위 "우선순위" 절을 참조해요.
여러 결과의 조정 규칙 다시 보기
함수 호출이 마지막에 오는 표현식 목록에서만 그 함수의 전체 반환값들이 전달된다는 규칙이 핵심이에요. 예를 들어 테이블 생성자 { f() }는 f의 반환값이 하나라면 그 값이 t[1]이 되고, 여러 개면 그 값들이 배열 필드로 펼쳐져요. return f() 는 f의 전체 반환을 그대로 되돌려줘요.