docs: revise spec to v0.1.6 and drop stale handoff
R8을 파생 반환 규칙으로 교체해 str.trim 계열의 슬라이스 반환을 표현 가능하게 하고, 결과를 지역 변수에 바인딩할 수 있게 한다. R6의 대여 구간을 마지막 사용 지점까지로 좁히고, R10에 전역 대여 금지를 추가해 호출 경계에서 배타성이 깨지던 구멍을 막는다. error.Name 코드 부여를 링크 심볼에서 드라이버의 emit 이전 단계로 옮겨 컴파일타임 상수로 만들고, str을 []u8과 별개 타입으로 분리한다. 문법 결함(struct 멤버의 pub, enum 배리언트 필드, catch/orelse 프로덕션, error_decl, expr 규범)과 CLI 플래그 산재, 문서-구현 드리프트를 함께 정리한다. 변경 사유는 SPEC.AUDIT.md에 기록했다. HANDOFF.md는 기준 SHA와 통신 프로토콜이 모두 낡아 제거한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PQm6oAvWX4Lp3iSN5AHGT
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Ferro 언어 명세 v0.1.5
|
||||
# Ferro 언어 명세 v0.1.6
|
||||
|
||||
DOS용 시스템 프로그래밍 언어. C만큼 빠르고, 메모리 안전성을 함수 단위 지역 검사만으로 보장한다.
|
||||
파일 확장자 `.fe`, 컴파일러 이름 `fec`, 심볼 파일 `.fei`.
|
||||
@@ -81,7 +81,7 @@ and or not orelse
|
||||
|---|---|---|
|
||||
| `[N]T` | 배열, 값 타입, N은 컴파일타임 상수 | `N * sizeof(T)` |
|
||||
| `[]T` | 슬라이스 (참조성, §5 R4 적용) | `(ptr, len)` |
|
||||
| `str` | `[]u8` 불변 별칭 | `(ptr, len)` |
|
||||
| `str` | 불변 바이트 슬라이스. `[]u8`과 **별개 타입** | `(ptr, len)` |
|
||||
| `^T` | 소유 포인터 (힙, 단일 소유자) | 포인터 |
|
||||
| `&T` | 공유 참조 | 포인터 |
|
||||
| `&mut T` | 배타 참조 | 포인터 |
|
||||
@@ -94,6 +94,7 @@ and or not orelse
|
||||
- **배열은 포인터로 붕괴하지 않는다.** 함수에 넘기려면 `arr[..]`로 슬라이스를 만들거나 `&arr` / `^[N]T`를 쓴다.
|
||||
- 슬라이싱: `arr[..]`, `arr[a..b]`(반개구간, 경계 검사), `arr[a..]`, `slice[a..b]`.
|
||||
- 소유 슬라이스가 필요하면 `^[]T`(길이 있는 힙 버퍼)를 쓴다. `mem.alloc_slice(T, n)`가 반환.
|
||||
- `str`은 별칭이 아니라 별개의 내장 타입이다. `[]u8`에서 `str`로는 `as str`로 변환할 수 있다(가변성 약화이므로 안전). `str`에서 `[]u8`로의 변환은 불변성을 깨므로 금지한다. `str`의 원소는 읽기만 가능하며 `s[i] = v`는 컴파일 에러다. 문자열 리터럴은 읽기 전용 저장 영역에 놓이므로 이 구분이 필요하다.
|
||||
|
||||
### 4.3 구조체
|
||||
|
||||
@@ -141,6 +142,7 @@ let v = p orelse default_node; // null이면 우변
|
||||
|
||||
- `?T`에서 T가 `^T`, `&T`, `*T`, `fn`이면 널 포인터를 널 표현으로 사용(크기 증가 없음).
|
||||
- 검사 없이 역참조 불가. `p.^`는 컴파일 에러, `p.?.^`가 필요.
|
||||
- `Some`과 `None`은 `if let`과 `match`의 패턴 위치에서만 옵셔널 해체를 의미하는 문맥 키워드다. 다른 위치에서는 일반 식별자이며 §3의 예약어가 아니다.
|
||||
|
||||
### 4.6 에러
|
||||
|
||||
@@ -162,18 +164,23 @@ fn read_all(path: str) -> IoError!^[]u8 {
|
||||
- `error` 선언은 `u16` 코드 집합. 코드 0은 "성공" 예약, 사용 불가.
|
||||
- `E!T` 함수만 `try`/`catch` 사용 가능.
|
||||
- `try e`: 에러면 현재 함수에서 즉시 반환(현재 함수도 에러 유니온을 반환해야 함).
|
||||
- `e catch |x| { ... }`: 블록은 값을 만들거나 `return`/`break`로 탈출.
|
||||
- `e catch default_value`: 짧은 형태.
|
||||
- `e catch |x| { ... }`: 블록은 값을 만들 수 없다. `return`/`break`/`continue`로 탈출하거나 `@trap()`으로 끝나야 한다. 값이 필요하면 아래 짧은 형태를 쓴다. (언어에 블록 표현식을 도입하지 않기 위한 선택. §13 참조.)
|
||||
- `e catch default_value`: 짧은 형태. 우변은 식이며 그 값이 결과가 된다.
|
||||
- 서로 다른 error 타입 간 자동 변환 없음. `!T`(기본 에러 집합 `core.Error`)로 통일하거나 명시 매핑.
|
||||
- 에러는 값이다. 언와인딩, 스택 추적, 소멸자 이외의 자동 정리 없음.
|
||||
- 실패를 복구하지 않고 트랩으로 바꾸려면 `expr catch @trap()`을 쓴다. v0.1.2에는 별도 `must` 키워드를 두지 않는다.
|
||||
|
||||
`error.Name`은 선언된 error 타입을 만들지 않고 기본 `core.Error`의 이름 있는
|
||||
멤버를 참조하는 익명 에러 값이다. 각 유닛은 사용한 이름을 `.fei`에 기록한다.
|
||||
최종 빌드에서 모든 유닛의 이름을 합치고 중복을 제거한 뒤 이름의 바이트순으로
|
||||
정렬하여 1부터 안정적인 `u16` 코드를 부여한다. 따라서 서로 다른 유닛의
|
||||
`error.Name`은 같은 값이고 빌드 순서와 병렬 컴파일에도 결과가 결정적이다.
|
||||
코드는 최종 링크용 생성 헤더의 심볼로 참조하므로 분리 컴파일에서도 일관된다.
|
||||
최종 빌드에서 **드라이버가 emit 단계 이전에** 모든 유닛의 `.fei`에서 사용된 이름을
|
||||
합치고 중복을 제거한 뒤 이름의 바이트순으로 정렬하여 1부터 `u16` 코드를 부여한다.
|
||||
따라서 서로 다른 유닛의 `error.Name`은 같은 값이고 빌드 순서와 병렬 컴파일에도
|
||||
결과가 결정적이며, 방출되는 C에서 **컴파일타임 정수 상수**이므로 `switch` case
|
||||
라벨로 쓸 수 있다.
|
||||
이름 집합이 바뀌면 코드가 재배치되므로 방출된 `.c`와 오브젝트 캐시는 전부
|
||||
무효화된다. `.fei` 자체는 코드가 아니라 이름만 기록하므로 무효화되지 않는다.
|
||||
유닛 단위 `--emit-c`는 전체 이름 집합을 알 수 없으므로 `--error-table=<파일>`로
|
||||
확정된 표를 받아야 하며, 없으면 컴파일 에러다.
|
||||
이름이 65,535개를 넘으면 컴파일 에러다. 명시적인 `error` 선언은 여전히 nominal
|
||||
타입이며, 같은 멤버 이름이나 숫자 코드를 가진 다른 선언 및 `core.Error`와 자동
|
||||
변환되지 않는다. `error.Name`의 타입은 `core.Error`이며 `core.Error!T` 또는
|
||||
@@ -185,6 +192,7 @@ fn read_all(path: str) -> IoError!^[]u8 {
|
||||
### 4.7 타입 동등성
|
||||
|
||||
이름 기반(nominal). 필드가 같아도 다른 이름이면 다른 타입. 별칭은 `const Alias = Type;`으로 만들며 완전 동일 취급.
|
||||
`str`은 이 의미의 별칭이 아니라 `[]u8`과 구별되는 별개의 내장 타입이다(§4.2).
|
||||
|
||||
---
|
||||
|
||||
@@ -212,19 +220,43 @@ fn read_all(path: str) -> IoError!^[]u8 {
|
||||
|
||||
이 한 줄이 라이프타임 표기 전체를 불필요하게 만든다.
|
||||
|
||||
**R5 (참조 수명).** 지역 참조 변수는 대상보다 오래 살 수 없다. R4 덕분에 대상은 항상 같은 함수의 지역 변수, 파라미터, 또는 전역이므로 스코프 중첩 확인만으로 검사된다.
|
||||
**R5 (참조 수명).** 지역 참조 변수는 대상보다 오래 살 수 없다. R4 덕분에 대상은 항상 같은 함수의 지역 변수, 파라미터, 또는 `static` 전역이므로 스코프 중첩 확인만으로 검사된다. 가변 전역에 대한 대여는 R10이 금지한다.
|
||||
|
||||
**R6 (배타성).** `&mut x`가 살아있는 동안 `x`에 대한 다른 참조 생성, 직접 읽기/쓰기, 이동이 금지된다. `&x`(공유)는 여러 개 동시 가능하지만 그동안 `x`에 쓰기/이동 금지. 참조의 생존 구간은 **참조 변수의 스코프 끝까지**(NLL 아님). 임시 참조(`f(&x)`)는 그 문장 끝까지.
|
||||
**R6 (배타성).** `&mut x`가 살아있는 동안 `x`에 대한 다른 참조 생성, 직접 읽기/쓰기, 이동이 금지된다. `&x`(공유)는 여러 개 동시 가능하지만 그동안 `x`에 쓰기/이동 금지.
|
||||
|
||||
참조의 생존 구간은 **참조 변수의 마지막 사용 지점까지**다. 그 이후에는 원본에 대한 접근·이동이 다시 허용된다. 조건부 흐름에서는 모든 경로의 마지막 사용 중 가장 나중 지점을 취한다. 임시 참조(`f(&x)`)는 그 문장 끝까지다.
|
||||
|
||||
이 판정은 함수 지역 liveness 분석이며 함수 밖 정보를 쓰지 않으므로 §1.2를 위반하지 않는다.
|
||||
|
||||
```fe
|
||||
var x: i32 = 0;
|
||||
let r = &mut x;
|
||||
r.^ = 1; // r의 마지막 사용
|
||||
x += 1; // OK — 여기서 r의 대여는 이미 끝났다
|
||||
```
|
||||
|
||||
**R7 (참조 무효화).** 참조 대상이 이동되거나 재대입되면 그 참조는 이후 사용 시 에러.
|
||||
|
||||
**R8 (반환값 예외).** 메서드는 `-> &T` / `-> &mut T`를 반환할 수 있다. 단, 첫 파라미터가 `&Self`/`&mut Self`이고 반환 참조는 그 self에서 파생된 것이어야 하며(컴파일러가 확인), **호출 결과는 그 문장 안에서만 사용 가능**하고 지역 변수에 바인딩할 수 없다.
|
||||
**R8 (파생 반환).** 함수는 다음 두 경우에 한해 `&T`, `&mut T`, `[]T`, `str`을 반환할 수 있다.
|
||||
|
||||
**(a) 파라미터 파생.** 파라미터 중 참조성 타입(`&T`, `&mut T`, `[]T`, `str`)이 **정확히 하나**이고, 반환값이 그 파라미터에서 파생된 것임을 컴파일러가 함수 본문만 보고 확인할 수 있을 때. 파생이란 슬라이싱, 인덱싱, 필드 접근, `&`/`&mut` 취함, 그리고 다른 R8(a) 함수 호출의 연쇄를 말한다. 반환의 가변성은 원본 이하여야 한다(`&T`에서 `&mut T`를 만들 수 없다). 메서드의 `self`도 이 "하나"에 해당한다.
|
||||
|
||||
**(b) 정적 파생.** 반환값이 문자열 리터럴 또는 `static` 선언에서 파생된 경우. 이때는 참조성 파라미터가 없어도 된다.
|
||||
|
||||
호출 지점에서 R8(a)의 결과는 **그 인자를 대여한 것으로 취급**한다. 즉 결과를 지역 변수에 바인딩할 수 있으며, 그 대여가 사는 동안 인자 원본에 R6·R7이 그대로 적용된다. R8(b)의 결과는 대여를 만들지 않는다.
|
||||
|
||||
```fe
|
||||
list.at(0).x = 5; // OK
|
||||
let r = list.at(0); // 에러: 반환 참조를 바인딩 불가
|
||||
pub fn trim(s: str) -> str { ... } // R8(a)
|
||||
let t = str.trim(line); // OK. line은 t의 대여 구간 동안 잠긴다
|
||||
|
||||
list.at(0).x = 5; // OK
|
||||
let r = list.at(0); // OK. list를 &mut로 대여
|
||||
list.push(1); // 에러: r이 list를 대여 중 (R6)
|
||||
|
||||
pub fn name() -> str { return "main"; } // R8(b)
|
||||
```
|
||||
장수하는 접근이 필요하면 인덱스(`usize`)나 핸들을 쓴다.
|
||||
|
||||
참조성 파라미터가 둘 이상인 함수는 어느 쪽에서 파생되었는지가 시그니처만으로 결정되지 않으므로 참조성 반환을 할 수 없다. 그런 함수가 필요하면 인덱스(`usize`)나 핸들을 반환한다.
|
||||
|
||||
**R9 (unsafe).** `unsafe {}` 안에서만 허용: raw 포인터 역참조, `*T` ↔ `^T`/`&T` 변환, `@ptr_cast`, `@seg_ptr`, `@volatile_*`, `@port_*`, `@as_far_fn`, `@call_far`, `asm`, `*_unchecked` 함수. R1~R8은 `unsafe` 안에서도 그대로 유지된다. 특히 `unsafe`가 참조 반환·저장이나 대여 검사를 끄지 않으며, 프로그래머가 명시적으로 raw 포인터를 경유한 부분만 컴파일러의 메모리 안전 보장 밖에 놓인다.
|
||||
|
||||
@@ -233,6 +265,15 @@ let r = list.at(0); // 에러: 반환 참조를 바인딩 불가
|
||||
- `interrupt fn` 안에서는 `shared var`에 직접 접근할 수 있다. 일반 함수와 `interrupt_safe fn`은 호출 문맥을 알 수 없으므로 명시적 `critical` 밖의 비원자 공유 접근이 금지된다.
|
||||
- `shared atomic var`는 타깃에서 한 명령으로 읽고 쓸 수 있는 정수/불린/포인터 스칼라에만 허용한다. 메인 흐름의 단일 읽기·쓰기에는 컴파일러가 필요한 최소 임계 구역을 생성한다. 복합 read-modify-write는 여전히 명시적 `critical {}`이 필요하다. 지원되지 않는 크기나 타입은 컴파일 에러다.
|
||||
- `interrupt fn`은 `interrupt_safe fn`만 호출할 수 있다. `interrupt_safe fn`은 힙 할당, DOS/DPMI 서비스, 블로킹 I/O, 부동소수점, `critical` 및 안전하지 않은 함수 호출을 사용할 수 없으며 컴파일러가 함수 본문만 보고 검증한다. 이 효과는 `.fei` 시그니처에 기록한다.
|
||||
- 전역에 대한 대여는 다음으로 제한한다. `static`(불변)은 `&`로 대여할 수 있다. 일반 전역 `var`는 `&`·`&mut` 모두 대여할 수 없으며 직접 읽기와 쓰기만 허용한다. `shared var`는 `critical` 안에서의 직접 접근만 허용하고 대여할 수 없다. 전역 값을 참조로 넘겨야 하면 지역 변수로 복사한 뒤 대여한다.
|
||||
- 이 제한의 근거는 R6다. 전역에 대한 대여가 살아 있는 동안 호출된 다른 함수가 같은 전역에 직접 접근할 수 있고, 그것은 함수 단위 지역 검사로 검출할 수 없다. 아래는 이 제한이 없으면 통과해 버리는 예다.
|
||||
|
||||
```fe
|
||||
var G: i32 = 0;
|
||||
fn f(r: &mut i32) { G = 5; } // r과 G가 같은 곳을 가리키는지 f는 알 수 없다
|
||||
fn g() { f(&mut G); } // 제한이 없으면 g의 지역 검사는 통과한다
|
||||
```
|
||||
|
||||
- 전역에는 `^T`나 `drop` 있는 타입을 둘 수 없다. `shared`, `atomic`, `critical`, `interrupt fn`은 v0.1.2에서 `bits16` 전용이며 `bits32`에서 사용하면 컴파일 에러다.
|
||||
|
||||
**R11 (재귀·그래프 구조).** `^T`는 R4의 2급 참조가 아니므로 소유가 한 방향인 단방향 리스트와 트리는 필드에 저장할 수 있다. 반면 양방향 리스트·순환·일반 그래프는 역방향 필드에 `^T`를 두면 R1의 단일 소유권을 위반하고 `&T`를 두면 R4를 위반한다. 이런 구조는 아레나/배열이 값을 소유하고 `u16`/`u32` 인덱스 핸들이 간선을 나타내도록 구현한다. 표준 라이브러리 `mem.Arena`를 사용할 수 있으며, 핸들 역참조 때 세대 번호 또는 경계 검사를 사용해 해제된 항목 접근을 막아야 한다.
|
||||
@@ -253,11 +294,13 @@ decl := ['pub'] (fn_decl | struct_decl | enum_decl | error_decl
|
||||
fn_decl := ['extern' string] [('interrupt' | 'interrupt_safe')] 'fn' ident
|
||||
'(' [param (',' param)*] ')' ['->' type] (block | ';')
|
||||
param := ['comptime'] ident ':' type
|
||||
struct_decl := ['packed'] 'struct' ident '{' field* fn_decl* '}'
|
||||
struct_decl := ['packed'] 'struct' ident '{' member* '}'
|
||||
member := ['pub'] (field | fn_decl)
|
||||
field := ident ':' type ','
|
||||
enum_decl := 'enum' ident '{' variant (',' variant)* [','] '}'
|
||||
variant := ident | ident '(' type ')' | ident '{' field* '}'
|
||||
error_decl := 'error' ident '{' (ident '=' int ',')* '}'
|
||||
variant := ident | ident '(' type ')' | ident '{' vfield* '}'
|
||||
vfield := ident ':' type ','
|
||||
error_decl := 'error' ident '{' ident '=' int (',' ident '=' int)* [','] '}'
|
||||
const_decl := 'const' ident [':' type] '=' expr ';'
|
||||
global_decl := 'static' ident ':' type '=' expr ';'
|
||||
| 'var' ident ':' type '=' expr ';'
|
||||
@@ -298,10 +341,20 @@ type := ident ['.' ident]
|
||||
| '[' expr ']' type | '[' ']' type
|
||||
| 'fn' '(' [type (',' type)*] ')' ['->' type]
|
||||
| ident '(' type (',' type)* ')' // 제네릭 인스턴스
|
||||
|
||||
catch_expr := expr 'catch' ['|' ident '|'] (expr | block)
|
||||
orelse_expr := expr 'orelse' expr
|
||||
```
|
||||
|
||||
`member`의 `pub`은 필드와 메서드 모두에 개별로 붙는다(§8). 필드와 메서드는 순서를
|
||||
섞어 쓸 수 있다. `catch`의 블록 형태는 값을 만들지 않으며 §4.6의 규칙을 따른다.
|
||||
|
||||
### 6.2 표현식 우선순위 (낮음 → 높음)
|
||||
|
||||
표현식 문법은 다음 우선순위 표가 규범이다. 각 단계는 명시가 없으면 좌결합 이항
|
||||
연산으로 전개하며, 단항·후위·기본 단계에 나열된 형태가 그대로 프로덕션이 된다.
|
||||
`catch`와 `orelse`의 구체적 형태는 §6.1을 따른다.
|
||||
|
||||
```
|
||||
1 orelse, catch
|
||||
2 or
|
||||
@@ -499,6 +552,21 @@ fec main.fe --emit-c -o out/ # 트랜스파일 결과만
|
||||
fec --dump-ast main.fe
|
||||
```
|
||||
|
||||
### 8.1 CLI 플래그 (전체)
|
||||
|
||||
| 플래그 | 의미 | 규정 |
|
||||
|---|---|---|
|
||||
| `--target=bits16\|bits32` | 타깃 선택 | §2 |
|
||||
| `--model=small\|large` | `bits16` 메모리 모델 | §2 |
|
||||
| `-o <경로>` | 출력 파일 또는 디렉터리 | §8 |
|
||||
| `-I <디렉터리>` | 유닛 검색 경로 추가 | §8 |
|
||||
| `--emit-c` | 트랜스파일 결과만 생성 | §8 |
|
||||
| `--dump-ast` | AST 덤프 | §8 |
|
||||
| `--no-checks` | 경계·오버플로·`.?` 검사 제거 | §7.4 |
|
||||
| `--strip-error-names` | 실행 파일에서 에러 이름 문자열 제거 | §4.6 |
|
||||
| `--error-table=<파일>` | 유닛 단위 `--emit-c`용 확정 에러 코드 표 | §4.6 |
|
||||
| `--deny-recursive-drop` | 재귀 drop 경고를 에러로 승격 | §5 R3 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 제네릭
|
||||
@@ -602,12 +670,16 @@ fec/
|
||||
lower.c/h AST → LIR. 소멸자/defer 삽입, try/catch/for/메서드 호출 전개.
|
||||
emit_c.c/h LIR → C. §11.4 규칙.
|
||||
generic.c/h 인스턴스 캐시, 토큰 재파싱.
|
||||
driver.c CLI, 유닛 의존 순서, .fei 캐시, 외부 C 컴파일러 호출.
|
||||
driver.c CLI, 유닛 의존 순서, .fei 캐시, 에러 이름 표 수집과 코드 부여(§4.6),
|
||||
외부 C 컴파일러 호출.
|
||||
rt/ 런타임 (C): trap, 힙, 슬라이스 헬퍼, DPMI/INT21 shim
|
||||
std/ 표준 라이브러리 (.fe)
|
||||
tests/ §12
|
||||
```
|
||||
|
||||
위는 목표 구조다. M5까지는 이름 해석·소유권·lower가 `check.c`/`emit_c.c`에 통합되어
|
||||
있다. R1~R8 전체를 다루는 M6 착수 시점에 `own.c/h`를 분리한다.
|
||||
|
||||
### 11.4 C 방출 규칙
|
||||
|
||||
| Ferro | C |
|
||||
@@ -621,11 +693,12 @@ fec/
|
||||
| `far X` | `__far X` (Watcom/Borland), bits32는 무시 |
|
||||
| `[N]T` | `struct { T a[N]; }` (값 의미론 유지, 붕괴 방지) |
|
||||
| `[]T` | `typedef struct { T* p; fe_usize n; } fe_slice_T;` |
|
||||
| `str` | `typedef struct { const uint8_t* p; fe_usize n; } fe_str;` |
|
||||
| `?T` (포인터류) | 원래 포인터, null 사용 |
|
||||
| `?T` (그 외) | `struct { unsigned char has; T v; }` |
|
||||
| `E!T` | `struct { uint16_t e; T v; }`, `!void`는 `uint16_t` |
|
||||
| struct | `struct fe_<unit>_<Name>` |
|
||||
| enum | `struct { uint8_t tag; union { ... } u; }` |
|
||||
| enum | `struct { uint8_t tag; union { ... } u; }`, 배리언트 256개 초과 시 `uint16_t tag` |
|
||||
| 함수 | `fe_<unit>_<name>`, 메서드는 `fe_<unit>_<Type>_<name>` |
|
||||
| 제네릭 인스턴스 | `fe_<unit>_<Name>__<타입인자맹글>` |
|
||||
|
||||
@@ -639,6 +712,8 @@ fec/
|
||||
- **`match`**: `switch (x.tag)`. 페이로드 바인딩은 지역 변수로 복사 또는 포인터.
|
||||
- **`asm`**: Intel 문법으로 고정 저장. Watcom/Borland는 그대로, gcc는 `__asm__(".intel_syntax noprefix\n" ...)`로 감싼다.
|
||||
- **`@print` 계열**: emit 단계에는 도달하지 않는다. lower 단계에서 이미 `fmt.write_*` 호출 나열로 전개되므로 emit_c는 일반 함수 호출로만 본다. `@print`는 각 호출의 오류 코드를 명시적으로 버리고 `void`가 되며, `@fprint`는 첫 오류를 전파하고, `@sprint`는 남은 버퍼 길이를 추적해 잘라 쓴 뒤 실제 길이를 반환한다. 포맷 문자열 조각은 각각 static const 문자열 리터럴로 방출하고 동일 문자열은 중복 제거.
|
||||
- **참조와 aliasing**: `&T` → `const T*` 방출은 aliasing 가정을 하지 않는다. `&mut T`에도 `restrict`를 붙이지 않으며, M13/M14 네이티브 백엔드도 noalias를 가정하지 않는다. R6의 배타성은 R10의 전역 대여 금지가 함께 성립할 때만 프로그램 전체에서 유지되므로, 방출 단계에서 이를 최적화 근거로 쓰지 않는다.
|
||||
- **에러 코드**: `error.Name`의 `u16` 코드는 드라이버가 emit 전에 확정한 정수 리터럴로 방출한다(§4.6). 따라서 에러 값에 대한 `match`도 `switch`로 방출할 수 있다.
|
||||
- **논리 연산**: `and`, `or`, `not`은 각각 C의 `&&`, `||`, `!`로 방출한다. `and`와 `or`는 C의 시퀀스 포인트와 단축 평가를 그대로 사용한다.
|
||||
- **임계 구역**: bits16의 `critical`은 진입 시 FLAGS를 저장한 뒤 `cli`하고 모든 이탈 경로에서 저장한 FLAGS를 복원한다. `shared atomic var`의 단일 접근도 같은 보존형 시퀀스를 사용하며 무조건 `sti`하지 않는다.
|
||||
- **방출 순서**: typedef 전방선언 → struct 정의(의존 위상 정렬) → 전역 → 함수 프로토타입 → 함수 본문.
|
||||
@@ -693,15 +768,17 @@ Uninit | Owned | Moved | MaybeMoved | Shared(n) | Exclusive
|
||||
|
||||
```
|
||||
tests/
|
||||
m<N>/ 마일스톤별 fixture. bad-*.fe는 fail 규약을 따른다
|
||||
pass/*.fe + *.expected 컴파일→실행→stdout 비교
|
||||
fail/*.fe 첫 줄 "// ERROR:<line>:<메시지 일부>"
|
||||
run16/*.fe bits16 빌드 후 QEMU FreeDOS 실행, 출력 파일 비교
|
||||
boot/ A/B 출력 비교, fixpoint 검증
|
||||
```
|
||||
|
||||
- `fail/`은 규칙별 최소 3개: R1(이동 후 사용), R3(직접 drop 호출), R4(필드에 참조), R5(스코프 초과), R6(배타성 위반), R7(무효화), R8(참조 반환 바인딩), R9(unsafe 밖 raw 역참조), match 완전성, 암묵 변환, 타입 불일치.
|
||||
- `fail/`은 규칙별 최소 3개: R1(이동 후 사용), R3(직접 drop 호출), R4(필드에 참조), R5(스코프 초과), R6(배타성 위반), R7(무효화), R8(참조성 파라미터 2개 이상에서 참조성 반환, 가변성 승격, 파생되지 않은 반환), R9(unsafe 밖 raw 역참조), R10(전역 `var` 대여), match 완전성, 암묵 변환, 타입 불일치, `str`을 `[]u8`로 변환, `str` 원소 쓰기.
|
||||
- 포매팅(§6.3.1) 전용 `fail/` 케이스: `{}` 개수 > 인자 개수, 인자 개수 > `{}` 개수, 미지원 verb(`{q}`), 런타임 값 포맷 문자열, 대응 `write_*` 없는 타입(예: struct), 닫히지 않은 `{`, `try @print(...)`(void).
|
||||
- 포매팅 `pass/` 케이스: 각 verb 1개 이상, `{{` 이스케이프, 인자 0개, `@print`의 오류 삼킴, `@sprint` 반환 길이/잘림 검증, `@fprint`를 `io.buf_writer`로 호출.
|
||||
- R6·R8 전용 `pass/` 케이스: 참조의 마지막 사용 이후 원본 재접근, 분기별 마지막 사용의 합류, R8(a) 결과를 지역 변수에 바인딩한 뒤 대여 종료 후 원본 접근, R8(b)의 문자열 리터럴 반환, `str.trim` 형태의 슬라이스 반환 연쇄.
|
||||
- `@compile_error`, `@as_far_fn`, `@call_far`의 comptime/타깃/unsafe 제약과 `error.Name`의
|
||||
`core.Error` 등록, 결정적 코드, `--strip-error-names`, `fmt.write_error`를 각각 pass/fail로 검증한다.
|
||||
- 각 마일스톤은 해당 기능의 pass/fail 테스트와 함께 완료한다.
|
||||
@@ -727,7 +804,8 @@ tests/
|
||||
| 슬라이스 패턴 매칭 | 편의 | — | 인덱스 비교 |
|
||||
| `inline fn` | 편의 | — | C 방출 시 `static inline` |
|
||||
| `must` 키워드 | 편의 | 실패를 트랩으로 바꾸는 문법 설탕일 뿐 핵심 의미론이 아님 | `expr catch @trap()` |
|
||||
| 라이프타임 표기 (`'a`) | **구조적 불가** | 전역 분석 필요, R4를 풀어야 함 | R4 (2급 참조), 인덱스 핸들 |
|
||||
| 블록 표현식 | 편의 | 값을 만드는 블록이 없으면 `catch`가 짧은 형태로 충분하고, 문법 표면이 작아진다 (§4.6) | `catch <식>`, `return`으로 탈출 |
|
||||
| 라이프타임 표기 (`'a`) | **구조적 불가** | 전역 분석 필요, R4를 풀어야 함 | R4 (2급 참조), R8 파생 반환, 인덱스 핸들 |
|
||||
| 선점형 스레드 | 구조적 불가 | DOS 기본 실행 모델에 없고 함수 단위 소유권 모델을 넘어서는 동기화가 필요 | — |
|
||||
| 매크로 / 전처리기 | **영구** | 도구 지원과 컴파일 속도 파괴 | `const`, `comptime if`, 제네릭, `@print` |
|
||||
| 예외 | 영구 | 언와인딩 기반 시설 없음, 숨은 비용 | 에러 유니온 |
|
||||
|
||||
Reference in New Issue
Block a user