버전 안내: 모듈은 Topaz v5.2 기능입니다. 모든 v5.1 단일 파일 프로그램은 빌드 엔트리로 쓰일 때 의미 변화 없이 그대로 유효한 v5.2 프로그램이며, 모듈 문법은 새 예약 키워드를 하나도 추가하지 않습니다.
import와export는 파일 항목 목록의 맨 앞에서만 인식되는 문맥적 머리말입니다.
Topaz 프로그램은 하나의 엔트리 파일에서 시작합니다. v5.2부터는
프로그램을 여러 파일로 나눌 수 있습니다. 각 .tpz 파일은 정확히
하나의 모듈이고, 이름은 프로젝트 루트 기준 상대 경로입니다.
두 파일짜리 프로그램
project/
main.tpz 엔트리
utils/
strings.tpz 모듈 utils.strings// utils/strings.tpz
export function shout(s: string) -> string {
return "{s}!"
}
export let greeting = "hello"// main.tpz
import utils.strings
let line = strings.shout(strings.greeting)
print(line) // "hello!"빌드는 utils.strings를 루트(엔트리 파일의 디렉터리, 또는 빌드에
명시한 루트) 아래의 utils/strings.tpz로 해석합니다. 파일 이름은
모듈 경로와 정확히 일치해야 합니다 — 해석은 대소문자를 구분하고,
유니코드 정규화나 케이스 폴딩 기준으로 충돌하는 파일 이름을
거부하므로, 한 머신에서 빌드되는 유닛은 어떤 파일시스템에서도 같은
방식으로 빌드됩니다.
임포트
Topaz의 임포트 형태는 정확히 두 가지입니다.
A형 — 네임스페이스 임포트
import utils.strings
let s = strings.shout("hi")import utils.strings는 이름 하나를 바인딩합니다: 마지막 세그먼트
strings. 점 경로는 표현식이 아니라 주소입니다 — utils 자체는
바인딩되지 않고, 암묵적 부모 모듈도 없습니다.
B형 — 선택 임포트
import utils.strings { shout, greeting }
let s = shout(greeting)선택 임포트는 나열한 익스포트 이름을 직접 바인딩합니다.
as로 별칭 붙이기
두 형태 모두 as로 바인딩되는 이름을 바꿀 수 있습니다:
import utils.strings as str
import net.url { encode as encodeUrl }
let s = str.shout(encodeUrl("a b"))별칭이 유일하게 바인딩되는 이름입니다. 두 슬롯은 조합되지
않습니다: import m as ns { x }는 v5.2 문법이 아닙니다.
임포트 규칙
- 임포트는 프롤로그를 이룹니다: 모든 임포트는 파일의 다른 모든 최상위 항목보다 앞에 옵니다.
- 한 모듈은 임포트하는 파일마다 최대 하나의 임포트 항목에만 나타날 수 있습니다 — A형 두 번, B형 두 번, 혼합 모두 중복 임포트 오류입니다.
- 한 임포트 목록 안에서 같은 원본 이름을 두 번 선택할 수 없고, 두 스펙이 같은 로컬 이름을 만들 수도 없습니다.
- 아무것도 익스포트하지 않는 모듈의 임포트는 오류입니다: Topaz에는 부작용 전용 임포트가 없습니다.
- 경로 루트
std와topaz는 예약되어 있습니다 —import std.io는 정적 오류입니다. 내장 표면은 프렐류드(v5.1 §22 표면 그대로)이며 임포트가 필요 없습니다.
익스포트와 가시성
모듈 최상위의 모든 것은 익스포트하지 않으면 비공개입니다. 가시성
수준은 정확히 둘이고, 익스포트 표기는 선언 앞에 붙이는 인라인
export 하나뿐입니다.
export function area(w: float, h: float) -> float { return w * h }
export type Size = { w: float, h: float }
export let defaultSize: Size = { w: 1.0, h: 1.0 }
export const maxSide = 4096
function clamp(v: float) -> float { // 비공개 헬퍼
if v > 4096.0 { return 4096.0 }
return v
}export는 런타임 비용이 없는 래퍼입니다 — 선언은 익스포트하지 않았을
때와 정확히 같게 동작합니다. 규칙:
- 익스포트되는
let은 정확히 식별자 하나를 바인딩합니다. 구조분해, 와일드카드, 반박 가능 패턴 익스포트는 정적 오류입니다. export let mut은 정적 오류입니다. 모듈 비공개let mut은 합법입니다; 가변 바인딩 셀은 모듈의 공개 표면에 결코 포함되지 않습니다(익스포트된 불변 바인딩이 그 자체로 가변인 값을 담는 것은 가능합니다).- 임포트한 바인딩은 읽기 전용입니다: 임포트한 이름이나
네임스페이스 경유(
strings.greeting = "x") 대입은 정적 오류입니다. - 익스포트된 시그니처, 익스포트 바인딩의 타입 표기, 익스포트 별칭 본문에 등장하는 타입 이름은 그 자체로 공개 해석 가능해야 합니다 — 원시/프렐류드 타입, 같은 모듈의 익스포트된 별칭, 또는 임포트한 모듈의 네임스페이스 한정 익스포트 타입. 비공개 별칭은 공개 표면으로 새어 나갈 수 없습니다.
네임스페이스 사용하기
A형 바인딩은 컴파일 타임 해석 객체이지 값이 아닙니다. 등장할 수 있는 위치는 정확히 두 곳 — 표현식의 멤버 접근 머리, 또는 한정 타입의 머리입니다:
// ui/theme.tpz
export type Style = { bold: bool }
export let defaultStyle: Style = { bold: false }// main.tpz
import ui.theme
let s: theme.Style = theme.defaultStyle // ns.Type과 ns.member
let b = theme.defaultStyle.bold // 그 뒤는 평범한 접근- 네임스페이스 조회는 멤버 이름을 정확히 하나 소비합니다. 그
뒤는 해석된 값에 대한 평범한 멤버 접근입니다 —
theme.defaultStyle.bold에서 네임스페이스가defaultStyle을 해석하고,.bold는 레코드 필드를 읽습니다. - 멤버는 익스포트된 이름이어야 합니다. 익스포트된 타입은 타입
위치에서, 익스포트된 값과 함수는 표현식 위치에서 씁니다 —
let x = theme.Style은 오류입니다. - 네임스페이스 자체는 전달·저장·반환할 수 없습니다:
let n = theme은 오류입니다. - 로컬·중첩 스코프는 평범한 스코프 규칙대로 모듈 최상위 이름을 가립니다. 이름 해석 순서는 로컬 먼저, 그다음 모듈 최상위, 그다음 프렐류드입니다.
초기화
임포트된 모듈은 엔트리의 첫 비-임포트 항목보다 먼저 즉시, 정확히 한
번 초기화됩니다. 순서는 결정적입니다: 모듈 간에는 의존성
순서(의존성이 먼저; 동순위는 사전식), 모듈 안에서는 위에서 아래로.
const 항목은 컴파일 타임이므로 런타임 단계가 없습니다.
임포트된 모듈 안에서 초기화식은 이미 초기화된 바인딩만 닿을 수 있습니다 — 같은 모듈의 앞선 항목이나 임포트한 것들. 앞으로 참조는 정적으로 거부되며, 검사는 람다와 헬퍼 함수 속까지 들여다봅니다:
// config.tpz — 거부됨
export let cache = compute() // 오류: `cache`보다 뒤에 선언된
export let limit = 100 // `limit`에 닿음
function compute() -> int { return limit * 2 }두 바인딩의 순서를 바꾸면 해결됩니다. 이 규칙(그리고 순환 거부와 결정적 순서) 덕분에 부분 초기화된 모듈을 관찰하는 모듈은 존재하지 않습니다 — 런타임 "임시 사각지대(TDZ)"가 없습니다.
임포트되는 파일에 관한 사실 두 가지:
- 최상위에는 임포트, 선언, 바인딩만 올 수 있습니다. 무언가를 하는
자유문 — 표현식 문장, 대입,
while,defer,return,break,continue— 은 임포트된 모듈에서 정적 오류입니다. 엔트리 파일은 v5.1의 자유를 그대로 유지합니다. 같은 파일이 엔트리로는 유효하고 임포트로는 무효일 수 있으며, 진단은 엔트리부터의 임포트 사슬을 보여 줍니다. - 초기화 중 폴트는 프로그램을 중단시키며, 잡을 수 없습니다.
순환은 오류
모든 임포트는 임포트 그래프에 간선을 만들고, 모든 임포트 순환은 정적 오류입니다 — 자기 자신을 임포트하는 경우를 포함해서, 임포트한 이름이 "타입뿐"이어도 마찬가지입니다. 진단은 서로 임포트하는 모듈 묶음마다 정식(canonical) 순환 경로 하나를 보고하므로, 같은 빌드 오류는 어디서나 똑같이 읽힙니다.
v5.2 모듈에 없는 것
v5.2 모듈 시스템은 의도적으로 작습니다:
-
패키지 매니저도, 매니페스트도 없습니다 — 루트 하나, 엔트리 하나, 루트 아래 경로가 전부입니다. 매니페스트류 파일은 언어적 의미가 없습니다.
-
재익스포트 문법이 없습니다 —
export import, 익스포트 목록(export { a, b }), 와일드카드 익스포트는 거부됩니다. 좁은 API는 평범한 익스포트로 직접 전달할 수 있습니다:TOPAZimport ui.theme export let defaultStyle = theme.defaultStyle export type Style = theme.Style정식 스타일은 여전히 정의한 모듈을 직접 임포트하는 쪽을 선호합니다.
-
use항목이 없습니다 —use는 인식되어 전용 진단으로 거부됩니다; 예약 상태입니다. -
문자열 경로가 없습니다 —
import "utils/strings"는 거부됩니다. 모듈 경로는 점으로 이은 식별자입니다.
이 형태들은 모듈 문법으로 진단됩니다(헷갈리는 베이스 문법 오류가 아니라 모듈 오류를 받습니다). 하지만 언어의 일부는 아닙니다.