루아 레퍼런스 매뉴얼 — 표준 라이브러리: 기본·코루틴·모듈

루아 레퍼런스 매뉴얼 — 표준 라이브러리: 기본·코루틴·모듈

이번 장에서는 루아 5.4 공식 레퍼런스 매뉴얼의 표준 라이브러리 중 세 구간을 함께 살펴볼게요. 섹션 6.1「기본 함수」는 루아의 뼈대가 되는 핵심 함수들이고, 섹션 6.2「코루틴 조작」은 실행을 잠시 멈추고 다시 이어가는 코루틴을 다루죠. 섹션 6.3「모듈」은 파일을 나눠 쓸 때 꼭 필요한 require와 패키지 시스템으로 마무리해요. 함수마다 시그니처(함수 이름과 인자 목록)를 그대로 보존했으니, 매뉴얼을 보면서 실제 코드를 따라갈 수 있도록 한 줄씩 옆에서 설명하는 방식으로 썼습니다.

출처: Lua 5.4 Reference Manual — https://www.lua.org/manual/5.4/manual.html (섹션 6.1–6.3)

본문

6.1 기본 함수 (Basic Functions)

기본 라이브러리는 루아의 핵심 기능을 제공해요. 만약 응용 프로그램에 이 라이브러리를 포함하지 않는다면, 그 기능 중 일부에 대해 직접 구현이 필요한지 잘 살펴봐야 합니다.

assert (v [, message])

인자 v의 값이 nil이나 false처럼 거짓이라면 오류를 일으켜요. 그렇지 않으면 모든 인자를 그대로 돌려주죠. 오류가 났을 때 message가 오류 객체가 되고, 생략하면 기본값으로 "assertion failed!"가 사용됩니다.

collectgarbage ([opt [, arg]])

이 함수는 가비지 컬렉터를 조작하는 범용 인터페이스예요. 첫 번째 인자 opt에 따라 서로 다른 일을 합니다.

  • "collect": 가비지 컬렉션 한 바퀴를 전체 수행해요. 기본 옵션이죠.
  • "stop": 가비지 컬렉터의 자동 실행을 멈춰요. 컬렉터는 다시 시작되기 전까지 명시적으로 호출했을 때만 실행됩니다.
  • "restart": 가비지 컬렉터의 자동 실행을 다시 시작해요.
  • "count": 루아가 사용 중인 전체 메모리를 Kbyte 단위로 돌려줘요. 이 값은 소수부를 가지므로, 1024를 곱하면 루아가 사용 중인 정확한 바이트 수가 나옵니다.
  • "step": 가비지 컬렉션 스텝을 한 번 수행해요. 스텝의 "크기"는 arg로 조절하죠. arg가 0이면 기본(더 쪼갤 수 없는) 스텝 하나를 수행하고, 0이 아니면 루아가 그만큼의 메모리(Kbyte)를 할당한 것처럼 수행합니다. 스텝이 컬렉션 한 바퀴를 끝냈으면 true를 돌려줘요.
  • "isrunning": 컬렉터가 실행 중인지(즉, 멈춰 있지 않은지)를 알려주는 불리언을 돌려줍니다.
  • "incremental": 컬렉터 모드를 점진(incremental)으로 바꿔요. 뒤에 숫자 세 개를 더 붙일 수 있는데, 각각 가비지 컬렉터의 pause, step multiplier, step size를 뜻해요(§2.5.1 참고). 0은 그 값을 바꾸지 않겠다는 뜻이에요.
  • "generational": 컬렉터 모드를 세대(generational) 방식으로 바꿔요. 뒤에 숫자 두 개를 붙일 수 있는데, 각각 minor multiplier와 major multiplier를 뜻해요(§2.5.2 참고). 0은 그 값을 바꾸지 않겠다는 뜻입니다.

가비지 컬렉션과 이 옵션들에 대한 자세한 내용은 §2.5를 참고하세요. 이 함수는 파이널라이저(finalizer) 안에서는 호출하면 안 됩니다.

dofile ([filename])

이름이 주어진 파일을 열고, 그 내용을 루아 청크(chunk)로 실행해요. 인자 없이 호출하면 표준 입력(stdin)의 내용을 실행하죠. 청크가 돌려준 모든 값을 그대로 돌려줍니다. 오류가 나면 dofile은 그 오류를 호출자에게 그대로 전파해요. 즉, dofile은 보호 모드(protected mode)로 실행되지 않습니다.

error (message [, level])

message를 오류 객체로 해서 오류를 일으켜요(§2.3 참고). 이 함수는 절대 반환하지 않습니다.

보통 error는 메시지가 문자열일 때 메시지 앞부분에 오류 위치 정보를 덧붙여요. level 인자는 그 오류 위치를 어떻게 구할지 결정하죠. level 1(기본값)이면 오류 위치는 error 함수가 호출된 지점이에요. level 2면 error를 호출한 함수가 호출된 위치를 가리키고, 그렇게 계속 올라갑니다. level 0을 넘기면 메시지에 오류 위치 정보를 붙이지 않아요.

_G

함수가 아니라 전역 환경(global environment)을 담고 있는 전역 변수예요(§2.2 참고). 루아 자신은 이 변수를 사용하지 않아서, 이 값을 바꿔도 어떤 환경에도 영향을 주지 않고 그 반대도 마찬가지입니다.

getmetatable (object)

object에 메타테이블이 없으면 nil을 돌려줘요. 메타테이블이 있는데 그 안에 __metatable 필드가 있으면 그 연관된 값을 돌려주고, 그렇지 않으면 object의 메타테이블을 그대로 돌려줍니다.

ipairs (t)

반복자 함수, 테이블 t, 그리고 0이라는 세 값을 돌려줘요. 그래서 다음과 같은 반복문을 만들 수 있죠.

     for i,v in ipairs(t) do <body> end

이 반복은 키–값 쌍 (1,t[1]), (2,t[2]), ... 를 처음으로 인덱스가 없는 지점까지 순회해요.

load (chunk [, chunkname [, mode [, env]]])

청크 하나를 로드해요.

chunk가 문자열이면 그 문자열이 청크 자체가 돼요. chunk가 함수라면 load는 그 함수를 반복 호출해서 청크 조각들을 얻어요. 각 호출은 이전 결과와 이어 붙일 문자열을 돌려줘야 하죠. 빈 문자열, nil, 또는 아무 값도 돌려주지 않는 호출이 오면 청크의 끝을 알리는 신호입니다.

구문 오류가 없다면 load는 컴파일된 청크를 함수로 돌려주고, 오류가 있으면 fail과 오류 메시지를 돌려줘요.

메인 청크를 로드하면 결과 함수는 정확히 하나의 업밸류(upvalue)를 갖는데, 그게 _ENV 변수예요(§2.2 참고). 다만 함수에서 만든 바이너리 청크(예: string.dump에서 만든 것)를 로드하면 결과 함수는 임의 개수의 업밸류를 가질 수 있고, 첫 업밸류가 _ENV일 거라는 보장은 없습니다. (메인이 아닌 함수는 _ENV 업밸류를 아예 갖지 않을 수도 있어요.)

어쨌든 결과 함수에 업밸류가 있다면, env 인자가 주어졌을 때 그 값으로, 아니면 전역 환경 값으로 첫 업밸류를 설정해요. 나머지 업밸류는 nil로 초기화됩니다. 모든 업밸류는 새로 만든 것이므로 다른 함수와 공유되지 않아요.

chunkname은 오류 메시지와 디버그 정보에서 청크 이름으로 사용됩니다(§4.7 참고). 생략하면 chunk가 문자열일 때는 chunk로, 아니면 "=(load)"로 기본값이 정해져요.

문자열 mode는 청크가 텍스트인지 바이너리(즉, 미리 컴파일된 청크)인지를 제어해요. "b"(바이너리만), "t"(텍스트만), "bt"(둘 다)가 될 수 있고 기본값은 "bt"입니다.

잘못 만들어진 바이너리 청크를 로드하는 건 안전해요. load가 적절한 오류를 알려 주니까요. 다만 루아는 바이너리 청크 안의 코드가 일관적인지 검사하지 않아서, 악의적으로 만들어진 바이트코드를 실행하면 인터프리터가 죽을 수 있다는 점을 알아 두세요.

loadfile ([filename [, mode [, env]]])

load와 비슷하지만, 파일 이름이 주어지면 그 파일에서, 아니면 표준 입력에서 청크를 가져옵니다.

next (table [, index])

프로그램이 테이블의 모든 필드를 순회할 수 있게 해줘요. 첫 인자는 테이블이고 두 번째 인자는 그 테이블의 인덱스예요. next를 호출하면 테이블의 다음 인덱스와 그에 연결된 값을 돌려줍니다. 두 번째 인자를 nil로 주면 초기 인덱스와 값을 돌려주고, 마지막 인덱스를 주거나 비어 있는 테이블에서 nil을 주면 nil을 돌려줘요. 두 번째 인자가 생략되면 nil로 간주됩니다. 특히 next(t)로 테이블이 비어 있는지 확인할 수 있죠.

인덱스가 열거되는 순서는 정의되어 있지 않아요. 숫자 인덱스라도 마찬가지예요. (숫자 순서로 순회하고 싶다면 숫자 for를 쓰세요.)

순회하는 동안 테이블에 존재하지 않는 필드에 값을 할당하면 안 됩니다. 다만 이미 있는 필드는 수정해도 돼요. 특히 이미 있는 필드를 nil로 바꾸는 것은 가능합니다.

pairs (t)

t__pairs 메타메서드가 있으면 t를 인자로 호출하고, 그 호출 결과 중 처음 세 값을 돌려줘요.

그렇지 않으면 next 함수, 테이블 t, nil이라는 세 값을 돌려줍니다. 그래서 다음 반복문을 만들 수 있어요.

     for k,v in pairs(t) do <body> end

이 반복은 테이블 t의 모든 키–값 쌍을 순회합니다. 순회 중 테이블을 수정할 때의 주의할 점은 next 함수 설명을 참고하세요.

pcall (f [, arg1, ···])

함수 f를 주어진 인자로 보호 모드에서 호출해요. 이는 f 안에서 일어난 어떤 오류도 전파되지 않는다는 뜻이에요. 대신 pcall이 오류를 잡아 상태 코드를 돌려줍니다. 첫 번째 결과가 상태 코드(불리언)인데, 오류 없이 성공했으면 true예요. 그 경우 pcall은 이 첫 결과 뒤에 호출의 모든 결과도 함께 돌려줍니다. 어떤 오류가 있어도 pcallfalse와 오류 객체를 돌려줘요. pcall이 잡은 오류는 메시지 핸들러를 호출하지 않는다는 점에 주의하세요.

print (···)

인자를 몇 개든 받아서 그 값을 stdout에 출력해요. 각 인자는 tostring과 같은 규칙으로 문자열로 변환됩니다.

print는 형식화된 출력용이 아니라, 값을 빠르게 확인하기 좋은 방법이에요. 예를 들어 디버깅할 때 유용하죠. 출력을 완전히 제어하려면 string.formatio.write를 쓰세요.

rawequal (v1, v2)

__eq 메타메서드를 호출하지 않고 v1v2와 같은지 판별해요. 불리언을 돌려줍니다.

rawget (table, index)

__index 메타값을 사용하지 않고 table[index]의 실제 값을 가져와요. table은 테이블이어야 하고 index는 어떤 값이든 될 수 있습니다.

rawlen (v)

__len 메타메서드를 호출하지 않고 객체 v의 길이를 돌려줘요. v는 테이블이나 문자열이어야 하고 결과는 정수입니다.

rawset (table, index, value)

__newindex 메타값을 사용하지 않고 table[index]value로 설정해요. table은 테이블, indexnil과 NaN이 아닌 어떤 값, value는 어떤 루아 값이든 될 수 있습니다.

이 함수는 table을 돌려줘요.

select (index, ···)

index가 숫자면 그 숫자 인덱스 이후의 모든 인자를 돌려줍니다. 음수는 끝에서부터 세는데, -1이 마지막 인자예요. 그 외에는 index가 문자열 "#"이어야 하고, 그 경우 select는 받은 나머지 인자의 총 개수를 돌려줍니다.

setmetatable (table, metatable)

주어진 테이블의 메타테이블을 설정해요. metatablenil이면 테이블의 메타테이블을 제거하죠. 원래 메타테이블에 __metatable 필드가 있으면 오류를 일으킵니다.

이 함수는 table을 돌려줘요. 테이블 이외의 타입 메타테이블을 루아 코드에서 바꾸려면 디버그 라이브러리(§6.10)를 사용해야 합니다.

tonumber (e [, base])

base 없이 호출하면 인자를 숫자로 변환하려고 시도해요. 인자가 이미 숫자이거나 숫자로 변환 가능한 문자열이면 그 숫자를 돌려주고, 아니면 fail을 돌려줍니다.

문자열 변환은 루아의 어휘 규칙(§3.1 참고)에 따라 정수나 부동소수가 될 수 있어요. 문자열은 앞뒤에 공백과 부호를 가질 수 있습니다.

base와 함께 호출하면 e는 그 진법의 정수 숫자로 해석할 문자열이어야 해요. base는 2부터 36까지의 정수입니다. 10보다 큰 진법에서 문자 'A'(대소문자 무관)는 10, 'B'는 11을 나타내고, 계속해서 'Z'는 35를 나타내죠. 문자열 e가 주어진 진법에서 유효한 숫자가 아니면 fail을 돌려줍니다.

tostring (v)

어떤 타입의 값이든 받아서 사람이 읽을 수 있는 형식의 문자열로 변환해요.

v의 메타테이블에 __tostring 필드가 있으면, tostring은 그에 해당하는 값을 v를 인자로 호출하고 그 결과를 자기 결과로 사용해요. 그 외에 v의 메타테이블에 문자열 값인 __name 필드가 있으면 그 문자열을 최종 결과에 사용할 수 있습니다.

숫자를 어떻게 변환할지 완전히 제어하려면 string.format을 쓰세요.

type (v)

유일한 인자의 타입을 문자열로 돌려줘요. 가능한 결과는 "nil"(값 nil이 아니라 문자열이라는 점 주의), "number", "string", "boolean", "table", "function", "thread", "userdata"입니다.

_VERSION

함수가 아니라 현재 실행 중인 루아 버전을 담은 문자열을 갖는 전역 변수예요. 현재 이 변수의 값은 "Lua 5.4"입니다.

warn (msg1, ···)

모든 인자(문자열이어야 함)를 이어 붙인 메시지로 경고를 발생시켜요.

관례상 '@'로 시작하는 한 덩어리 메시지는 경고 시스템 자체에 보내는 제어 메시지로 취급됩니다. 특히 루아의 표준 경고 함수는 제어 메시지 "@off"(경고 발생 중지)와 "@on"(경고 발생 재시작)을 인식하고, 모르는 제어 메시지는 무시해요.

xpcall (f, msgh [, arg1, ···])

pcall과 비슷하지만, 새 메시지 핸들러 msgh를 설정한다는 점이 달라요.

6.2 코루틴 조작 (Coroutine Manipulation)

이 라이브러리는 코루틴을 다루는 연산들을 담고 있으며, 모두 coroutine 테이블 안에 있어요. 코루틴에 대한 일반적인 설명은 §2.6을 참고하세요.

coroutine.close (co)

코루틴 co를 닫아요. 즉, 대기 중인 닫힐 변수(to-be-closed variable)들을 모두 닫고 코루틴을 죽은(dead) 상태로 만듭니다. 주어진 코루틴은 죽었거나 일시 중단(suspended)된 상태여야 해요. 오류가 나면(코루틴을 멈춘 원래 오류든 닫기 메서드의 오류든) false와 오류 객체를 돌려주고, 아니면 true를 돌려줍니다.

coroutine.create (f)

몸체가 f인 새 코루틴을 만들어요. f는 함수여야 하고, 타입이 "thread"인 새 코루틴 객체를 돌려줍니다.

coroutine.isyieldable ([co])

코루틴 co가 yield할 수 있으면 true를 돌려줘요. co의 기본값은 현재 실행 중인 코루틴입니다. 코루틴이 메인 스레드가 아니고, yield 불가능한 C 함수 안에 있지 않다면 yield할 수 있어요.

coroutine.resume (co [, val1, ···])

코루틴 co의 실행을 시작하거나 계속해요. 코루틴을 처음 resume하면 몸체가 실행을 시작하죠. 이때 val1, ... 값들이 몸체 함수의 인자로 전달됩니다. 코루틴이 yield 했었다면 resume은 그것을 다시 시작하고, val1, ... 값들이 yield의 결과로 전달돼요.

코루틴이 오류 없이 실행되면 resumetrue와 함께, 코루틴이 yield 할 때 yield에 넘긴 값들이나 코루틴이 끝날 때 몸체 함수가 돌려준 값들을 돌려줍니다. 어떤 오류가 있어도 resumefalse와 오류 메시지를 돌려줘요.

coroutine.running ()

실행 중인 코루틴과 불리언 하나를 돌려줍니다. 실행 중인 코루틴이 메인 코루틴이면 그 불리언은 true예요.

coroutine.status (co)

코루틴 co의 상태를 문자열로 돌려줘요. "running"은 실행 중(즉, status를 호출한 코루틴 자신)일 때, "suspended"yield 호출에서 일시 중단되었거나 아직 시작되지 않았을 때, "normal"은 활성이지만 실행 중이 아닐 때(즉, 다른 코루틴을 resume 했을 때), "dead"는 몸체 함수를 끝냈거나 오류로 멈췄을 때입니다.

coroutine.wrap (f)

몸체가 f인 새 코루틴을 만들어요. f는 함수여야 하고, 호출할 때마다 코루틴을 resume하는 함수를 돌려줍니다. 이 함수에 넘긴 인자들은 resume의 추가 인자로 동작해요. 돌려주는 값은 resume이 돌려주는 값과 같지만, 첫 번째 불리언은 제외됩니다. 오류가 나면 이 함수는 코루틴을 닫고 그 오류를 전파해요.

coroutine.yield (···)

호출한 코루틴의 실행을 일시 중단시켜요. yield에 넘긴 인자들은 resume의 추가 결과로 전달됩니다.

6.3 모듈 (Modules)

패키지 라이브러리는 루아에서 모듈을 로드하는 기본 기능을 제공해요. 전역 환경에 함수 하나를 직접 내보내는데, 그것이 require예요. 나머지는 모두 package 테이블 안에 담겨 있습니다.

require (modname)

주어진 모듈을 로드해요. 먼저 package.loaded 테이블을 들여다봐서 modname이 이미 로드됐는지 확인합니다. 이미 로드되어 있다면 requirepackage.loaded[modname]에 저장된 값을 돌려주죠. (이 경우 두 번째 결과가 없다는 것이 이 호출이 모듈을 로드할 필요가 없었다는 신호예요.) 아니면 모듈의 *로더(loader)*를 찾으려고 시도합니다.

로더를 찾기 위해 requirepackage.searchers 테이블의 안내를 받아요. 이 테이블의 각 항목은 특정 방식으로 모듈을 찾는 검색 함수입니다. 이 테이블을 바꾸면 require가 모듈을 찾는 방식을 바꿀 수 있죠. 아래 설명은 기본 설정의 package.searchers를 기준으로 한 겁니다.

먼저 requirepackage.preload[modname]을 조회해요. 값이 있으면 그 값(함수여야 함)이 로더가 됩니다. 아니면 package.path에 저장된 경로로 루아 로더를 찾고, 그것도 실패하면 package.cpath에 저장된 경로로 C 로더를 찾아요. 그것도 실패하면 all-in-one 로더를 시도합니다(§package.searchers 참고).

로더를 찾으면 require는 두 인자로 로더를 호출해요. 하나는 modname, 다른 하나는 검색 함수가 함께 돌려준 로더 데이터라는 추가 값입니다. 로더 데이터는 모듈에 유용한 어떤 값이든 될 수 있는데, 기본 검색 함수에서는 로더가 어디서 발견됐는지를 나타내요. (예를 들어 로더가 파일에서 왔다면 이 추가 값이 파일 경로예요.) 로더가 어떤 non-nil 값을 돌려주면 require는 그 값을 package.loaded[modname]에 할당해요. 로더가 non-nil 값을 돌려주지 않고 package.loaded[modname]에도 값을 할당하지 않았다면, require는 이 항목에 true를 할당합니다. 어떤 경우든 requirepackage.loaded[modname]의 최종 값을 돌려줘요. 그 값 외에도 require는 검색 함수가 돌려준 로더 데이터를 두 번째 결과로 돌려주는데, 그것이 require가 모듈을 어떻게 찾았는지 알려줍니다.

모듈을 로드하거나 실행하는 중 오류가 있거나, 모듈의 로더를 전혀 찾지 못하면 require는 오류를 일으켜요.

package.config

패키지의 컴파일 시 설정 일부를 설명하는 문자열이에요. 이 문자열은 줄들의 연속입니다.

  • 첫 줄은 디렉터리 구분자 문자열이에요. 기본값은 Windows에서 '\', 그 외 모든 시스템에서 '/'입니다.
  • 둘째 줄은 경로 안에서 템플릿을 구분하는 문자예요. 기본값은 ';'입니다.
  • 셋째 줄은 템플릿 안의 치환 지점을 표시하는 문자열이에요. 기본값은 '?'입니다.
  • 넷째 줄은 Windows의 경로에서 실행 파일의 디렉터리로 치환되는 문자열이에요. 기본값은 '!'입니다.
  • 다섯째 줄은 luaopen_ 함수 이름을 만들 때 그 뒤의 모든 텍스트를 무시하라는 표시예요. 기본값은 '-'입니다.

package.cpath

C 로더를 찾기 위해 require가 사용하는 경로를 담은 문자열이에요.

루아는 루아 경로 package.path를 초기화하는 것과 같은 방식으로 C 경로 package.cpath를 초기화하는데, 환경 변수 LUA_CPATH_5_4LUA_CPATH, 또는 luaconf.h에 정의된 기본 경로를 사용합니다.

package.loaded

require가 어떤 모듈이 이미 로드됐는지 제어하는 데 쓰는 테이블이에요. 모듈 modname을 require할 때 package.loaded[modname]이 false가 아니면, require는 거기에 저장된 값을 그냥 돌려줍니다.

이 변수는 실제 테이블에 대한 참조일 뿐이라, 이 변수에 할당해도 require가 쓰는 테이블은 바뀌지 않아요. 실제 테이블은 C 레지스트리(§4.3 참고)에 문자열 키 LUA_LOADED_TABLE로 저장되어 있습니다.

package.loadlib (libname, funcname)

호스트 프로그램을 C 라이브러리 libname과 동적으로 연결해요.

funcname"*"면 라이브러리와 연결만 하고, 라이브러리가 내보낸 심볼들을 다른 동적 연결 라이브러리들이 쓸 수 있게 해줘요. 그 외에는 라이브러리 안에서 funcname 함수를 찾아 그 함수를 C 함수로 돌려줍니다. 따라서 funcnamelua_CFunction 프로토타입(§lua_CFunction 참고)을 따라야 해요.

이것은 저수준 함수예요. 패키지와 모듈 시스템을 완전히 우회하죠. require와 달리 경로 탐색을 하지 않고 확장자를 자동으로 붙이지도 않아요. libname은 필요하다면 경로와 확장자를 포함한 C 라이브러리의 완전한 파일 이름이어야 해요. funcname은 C 라이브러리가 내보내는 정확한 이름이어야 합니다(사용한 C 컴파일러와 링커에 따라 달라질 수 있어요).

이 기능은 ISO C가 지원하지 않아서 일부 플랫폼에서만 사용할 수 있어요(Windows, Linux, Mac OS X, Solaris, BSD와 dlfcn 표준을 지원하는 그 밖의 Unix 시스템).

이 함수는 본질적으로 안전하지 않아요. 시스템의 어떤 읽기 가능한 동적 라이브러리에 있는 어떤 함수든 루아가 호출할 수 있게 해 주니까요. (루아는 함수가 적절한 프로토타입을 갖고 적절한 프로토콜을 따른다고 가정하고 호출합니다(§lua_CFunction 참고). 그래서 임의의 동적 라이브러리 안의 임의의 함수를 호출하면 대개 액세스 위반을 일으킵니다.)

package.path

루아 로더를 찾기 위해 require가 사용하는 경로를 담은 문자열이에요.

시작 시 루아는 환경 변수 LUA_PATH_5_4LUA_PATH의 값으로, 그 환경 변수들이 정의되어 있지 않으면 luaconf.h에 정의된 기본 경로로 이 변수를 초기화해요. 환경 변수 값 안의 ";;"는 기본 경로로 치환됩니다.

package.preload

특정 모듈의 로더를 저장하는 테이블이에요(§require 참고).

이 변수는 실제 테이블에 대한 참조일 뿐이라, 이 변수에 할당해도 require가 쓰는 테이블은 바뀌지 않아요. 실제 테이블은 C 레지스트리(§4.3 참고)에 문자열 키 LUA_PRELOAD_TABLE로 저장되어 있습니다.

package.searchers

require가 모듈을 어떻게 찾을지 제어하는 데 쓰는 테이블이에요.

이 테이블의 각 항목은 *검색 함수(searcher function)*입니다. 모듈을 찾을 때 require는 이 검색 함수들을 오름차순으로 하나씩 호출하는데, 인자로는 모듈 이름(require에 준 인자) 하나만 넘겨요. 검색 함수가 모듈을 찾으면 모듈 로더인 또 다른 함수와, 그 로더에 전달되고 require가 두 번째 결과로 돌려줄 로더 데이터라는 추가 값을 돌려줍니다. 모듈을 찾지 못하면 왜 못 찾았는지 설명하는 문자열을 돌려주죠(할 말이 없으면 nil).

루아는 이 테이블을 네 개의 검색 함수로 초기화해요.

첫 번째 검색 함수는 그냥 package.preload 테이블에서 로더를 찾습니다.

두 번째 검색 함수는 package.path에 저장된 경로를 사용해 루아 라이브러리로 로더를 찾아요. 검색은 package.searchpath 함수에서 설명한 대로 이루어집니다.

세 번째 검색 함수는 package.cpath 변수가 주는 경로를 사용해 C 라이브러리로 로더를 찾아요. 역시 검색은 package.searchpath 함수에서 설명한 대로 이루어지죠. 예를 들어 C 경로가 다음 문자열이라면,

     "./?.so;./?.dll;/usr/local/?/init.so"

모듈 foo의 검색 함수는 ./foo.so, ./foo.dll, /usr/local/foo/init.so 파일을 그 순서대로 열려고 시도합니다. C 라이브러리를 찾으면 이 검색 함수는 먼저 동적 연결 기능으로 응용 프로그램을 그 라이브러리와 연결해요. 그다음 라이브러리 안에서 로더로 쓸 C 함수를 찾죠. 이 C 함수의 이름은 문자열 "luaopen_"에, 각 점을 밑줄로 바꾼 모듈 이름을 이어 붙인 것입니다. 게다가 모듈 이름에 하이픈이 있으면 첫 하이픈 이후(그 하이픈 포함)의 접미사는 제거돼요. 예를 들어 모듈 이름이 a.b.c-v2.1이면 함수 이름은 luaopen_a_b_c가 됩니다.

네 번째 검색 함수는 all-in-one 로더를 시도해요. 주어진 모듈의 루트 이름에 대한 라이브러리를 C 경로에서 찾죠. 예를 들어 a.b.c를 require할 때 a에 대한 C 라이브러리를 찾습니다. 찾으면 그 안에서 서브모듈의 open 함수를 찾아요. 이 예에서는 luaopen_a_b_c가 되겠죠. 이 기능 덕에 하나의 패키지가 여러 C 서브모듈을 단일 라이브러리로 묶을 수 있고, 각 서브모듈은 원래의 open 함수를 유지합니다.

첫 번째 검색 함수(preload)를 제외한 모든 검색 함수는 추가 값으로 package.searchpath가 돌려준 모듈이 발견된 파일 경로를 돌려줘요. 첫 번째 검색 함수는 항상 문자열 ":preload:"를 돌려줍니다.

검색 함수는 루아에서 오류를 일으키지 않고 부수 효과도 없어야 해요. (C에서는 예를 들어 응용 프로그램을 라이브러리와 연결하는 식으로 부수 효과가 있을 수 있습니다.)

package.searchpath (name, path [, sep [, rep]])

주어진 path에서 주어진 name을 검색해요.

경로는 세미콜론으로 구분된 템플릿들의 연속인 문자열이에요. 각 템플릿에 대해 이 함수는 템플릿 안의 물음표(있으면) 각각을, name에서 sep(기본적으로 점)의 모든 출현을 rep(기본적으로 시스템 디렉터리 구분자)으로 바꾼 사본으로 치환하고, 그 결과 파일 이름을 열어 보려고 시도합니다.

예를 들어 경로가 다음 문자열이라면,

     "./?.lua;./?.lc;/usr/local/?/init.lua"

foo.a라는 이름의 검색은 ./foo/a.lua, ./foo/a.lc, /usr/local/foo/a/init.lua 파일을 그 순서대로 열려고 시도해요.

읽기 모드로 열 수 있는(파일을 닫은 후) 첫 파일의 결과 이름을 돌려주고, 어느 것도 성공하지 못하면 fail과 오류 메시지를 돌려줘요. (이 오류 메시지는 열어 보려고 시도한 모든 파일 이름을 나열합니다.)

더 알아보기

  • 이 문서는 루아 5.4 공식 매뉴얼의 섹션 6.1~6.3을 옮긴 요약본이에요. 원문에서 각 함수의 자세한 조건과 §2.x 상세 섹션을 함께 보면 더 정확해요: https://www.lua.org/manual/5.4/manual.html
  • 기본 함수(assert, pcall, xpcall)는 오류 처리의 기초라서, 섹션 2.3(오류 처리)을 먼저 읽어 두면 훨씬 수월해요.
  • 코루틴 개념 자체가 궁금하다면 섹션 2.6(코루틴)을, 그리고 yield·resume의 실제 동작을 작은 예제로 실험해 보는 걸 추천해요.
  • require와 패키지 시스템을 깊이 이해하고 싶다면 섹션 6.3 전체와 package.searchers 설명을 짝지어 보세요. 모듈을 직접 만들어 로드 순서를 확인해 보면 이해가 빨라져요.
  • 다음 표준 라이브러리 섹션(문자열, 테이블, 수학 등)으로 이어서 학습하면 기본기를 탄탄히 다질 수 있어요.