docs: resolve language audit for Ferro v0.1.2

This commit is contained in:
2026-08-16 06:44:39 +09:00
parent c7b0217d43
commit 990068f424
+81 -32
View File
@@ -1,4 +1,4 @@
# Ferro 언어 명세 v0.1 # Ferro 언어 명세 v0.1.2
DOS용 시스템 프로그래밍 언어. C만큼 빠르고, 메모리 안전성을 함수 단위 지역 검사만으로 보장한다. DOS용 시스템 프로그래밍 언어. C만큼 빠르고, 메모리 안전성을 함수 단위 지역 검사만으로 보장한다.
파일 확장자 `.fe`, 컴파일러 이름 `fec`, 심볼 파일 `.fei`. 파일 확장자 `.fe`, 컴파일러 이름 `fec`, 심볼 파일 `.fei`.
@@ -28,7 +28,7 @@ DOS용 시스템 프로그래밍 언어. C만큼 빠르고, 메모리 안전성
| 메모리 모델 | small, large | flat | | 메모리 모델 | small, large | flat |
| 시스템 호출 | INT 21h 직접 | DPMI 서비스 + 실모드 콜백 | | 시스템 호출 | INT 21h 직접 | DPMI 서비스 + 실모드 콜백 |
- CLI: `fec main.fe --target=bits16|bits32 [--model=small|large]` - CLI: `fec main.fe --target=bits16|bits32 [--model=small|large] [--strip-error-names]`
- 소스 분기: `comptime if @bits == 16 { ... } else { ... }` - 소스 분기: `comptime if @bits == 16 { ... } else { ... }`
- `bits32`에서 `far` 키워드를 쓰면 컴파일 에러. - `bits32`에서 `far` 키워드를 쓰면 컴파일 에러.
- 표준 라이브러리는 코어 공용, `sys` 유닛만 타깃별 구현. - 표준 라이브러리는 코어 공용, `sys` 유닛만 타깃별 구현.
@@ -47,10 +47,11 @@ DOS용 시스템 프로그래밍 언어. C만큼 빠르고, 메모리 안전성
**예약어:** **예약어:**
``` ```
unit import pub fn struct enum error const static var let mut unit import pub fn struct packed enum error const static var let
if else while for in match return break continue defer if else while for in match return break continue defer
unsafe comptime asm try catch as extern interrupt far unsafe critical shared atomic comptime asm try catch as extern interrupt interrupt_safe far
true false null self Self type true false null undefined self Self type
and or not orelse
``` ```
--- ---
@@ -164,6 +165,21 @@ fn read_all(path: str) -> IoError!^[]u8 {
- `e catch default_value`: 짧은 형태. - `e catch default_value`: 짧은 형태.
- 서로 다른 error 타입 간 자동 변환 없음. `!T`(기본 에러 집합 `core.Error`)로 통일하거나 명시 매핑. - 서로 다른 error 타입 간 자동 변환 없음. `!T`(기본 에러 집합 `core.Error`)로 통일하거나 명시 매핑.
- 에러는 값이다. 언와인딩, 스택 추적, 소멸자 이외의 자동 정리 없음. - 에러는 값이다. 언와인딩, 스택 추적, 소멸자 이외의 자동 정리 없음.
- 실패를 복구하지 않고 트랩으로 바꾸려면 `expr catch @trap()`을 쓴다. v0.1.2에는 별도 `must` 키워드를 두지 않는다.
`error.Name`은 선언된 error 타입을 만들지 않고 기본 `core.Error`의 이름 있는
멤버를 참조하는 익명 에러 값이다. 각 유닛은 사용한 이름을 `.fei`에 기록한다.
최종 빌드에서 모든 유닛의 이름을 합치고 중복을 제거한 뒤 이름의 바이트순으로
정렬하여 1부터 안정적인 `u16` 코드를 부여한다. 따라서 서로 다른 유닛의
`error.Name`은 같은 값이고 빌드 순서와 병렬 컴파일에도 결과가 결정적이다.
코드는 최종 링크용 생성 헤더의 심볼로 참조하므로 분리 컴파일에서도 일관된다.
이름이 65,535개를 넘으면 컴파일 에러다. 명시적인 `error` 선언은 여전히 nominal
타입이며, 같은 멤버 이름이나 숫자 코드를 가진 다른 선언 및 `core.Error`와 자동
변환되지 않는다. `error.Name`의 타입은 `core.Error`이며 `core.Error!T` 또는
축약형 `!T`를 반환하는 함수에서만 직접 반환할 수 있다.
`--strip-error-names`를 사용하면 실행 파일과 런타임 오류 문자열에서 이름을
제거하지만 숫자 코드와 `.fei`의 타입/코드 일관성 정보는 유지한다.
`fmt.write_error`는 이 정책에 따라 `core.Error` 값을 이름 또는 코드로 출력한다.
### 4.7 타입 동등성 ### 4.7 타입 동등성
@@ -179,7 +195,7 @@ fn read_all(path: str) -> IoError!^[]u8 {
**R2 (Copy 타입).** 다음은 이동 대신 복사된다: 정수, `bool`, `char`, raw 포인터 `*T`, 참조 `&T`, 함수 포인터, 그리고 모든 필드가 Copy이면서 `drop`이 없는 struct/enum/배열. `^T``&mut T`는 Copy가 아니다. **R2 (Copy 타입).** 다음은 이동 대신 복사된다: 정수, `bool`, `char`, raw 포인터 `*T`, 참조 `&T`, 함수 포인터, 그리고 모든 필드가 Copy이면서 `drop`이 없는 struct/enum/배열. `^T``&mut T`는 Copy가 아니다.
**R3 (소멸자, RAII).** `^T`는 소유자 스코프 종료 또는 재대입 시 `drop` 호출 후 해제. struct에 `fn drop(self: &mut Self)`가 있으면 그 값의 스코프 종료 시 자동 호출되며, 이어서 필드들의 drop이 선언 역순으로 호출된다. `drop`을 직접 호출하는 것은 컴파일 에러(`mem.destroy(x)` 사용). **R3 (소멸자, RAII).** `^T`는 소유자 스코프 종료 또는 재대입 시 `drop` 호출 후 해제. struct에 `fn drop(self: &mut Self)`가 있으면 그 값의 스코프 종료 시 자동 호출되며, 이어서 필드들의 drop이 선언 역순으로 호출된다. `drop`을 직접 호출하는 것은 컴파일 에러(`mem.destroy(x)` 사용). `^Self` 또는 `?^Self`를 재귀적으로 포함한 타입은 기본 필드 drop이 스택 깊이에 비례할 수 있으므로 컴파일러가 경고한다. 이런 연결 구조는 `mem.replace(&mut link, null)`로 소유 링크를 하나씩 꺼내 반복 해제하고 필드를 빈 값으로 남기는 사용자 `drop`을 정의해야 하며, `--deny-recursive-drop`으로 경고를 에러로 바꿀 수 있다.
**R4 (참조는 2급 값).** `&T`, `&mut T`, `[]T`는 다음 위치에만 존재할 수 있다: **R4 (참조는 2급 값).** `&T`, `&mut T`, `[]T`는 다음 위치에만 존재할 수 있다:
- 함수 파라미터 - 함수 파라미터
@@ -209,11 +225,16 @@ let r = list.at(0); // 에러: 반환 참조를 바인딩 불가
``` ```
장수하는 접근이 필요하면 인덱스(`usize`)나 핸들을 쓴다. 장수하는 접근이 필요하면 인덱스(`usize`)나 핸들을 쓴다.
**R9 (unsafe).** `unsafe {}` 안에서만 허용: raw 포인터 역참조, `*T``^T`/`&T` 변환, `@ptr_cast`, `@seg_ptr`, `@volatile_*`, `@port_*`, `asm`, `*_unchecked` 함수, 전역 `var` 접근. R1~R8은 `unsafe` 안에서도 유지되며, raw 포인터를 경유해야만 우회된다. **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 포인터를 경유한 부분만 컴파일러의 메모리 안전 보장 밖에 놓인다.
**R10 (전역).** `static`은 불변이며 컴파일타임 상수 초기화만 가능. `var` 전역은 허용되나 접근이 `unsafe`. 전역에 `^T``drop` 있는 타입 금지. **R10 (전역과 인터럽트 공유).** `static`은 불변이며 컴파일타임 상수 초기화만 가능하다. 일반 전역 `var`의 읽기와 쓰기는 안전하며 `unsafe`가 필요 없다. 인터럽트 핸들러와 메인 흐름이 함께 접근하는 값은 반드시 `shared var`로 선언하고 다음 규칙을 적용한다.
- 메인 흐름은 `critical {}` 안에서만 `shared var`에 접근할 수 있다. 진입 시 플래그를 저장하고 인터럽트를 막으며, 정상 종료·`return`·`break`·`continue`·에러 전파를 포함한 모든 이탈 경로에서 원래 플래그를 복원한다.
- `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` 시그니처에 기록한다.
- 전역에는 `^T``drop` 있는 타입을 둘 수 없다. `shared`, `atomic`, `critical`, `interrupt fn`은 v0.1.2에서 `bits16` 전용이며 `bits32`에서 사용하면 컴파일 에러다.
**R11 (그래프 구조).** 참조 순환이 필요한 자료구조는 아레나 + 인덱스 핸들로 표현한다. 표준 라이브러리 `mem.Arena``u16`/`u32` 인덱스를 쓴다. **R11 (재귀·그래프 구조).** `^T`는 R4의 2급 참조가 아니므로 소유가 한 방향인 단방향 리스트와 트리는 필드에 저장할 수 있다. 반면 양방향 리스트·순환·일반 그래프는 역방향 필드에 `^T`를 두면 R1의 단일 소유권을 위반하고 `&T`를 두면 R4를 위반한다. 이런 구조는 아레나/배열이 값을 소유하고 `u16`/`u32` 인덱스 핸들이 간선을 나타내도록 구현한다. 표준 라이브러리 `mem.Arena`를 사용할 수 있으며, 핸들 역참조 때 세대 번호 또는 경계 검사를 사용해 해제된 항목 접근을 막아야 한다.
--- ---
@@ -228,7 +249,7 @@ import := 'import' ident ';'
decl := ['pub'] (fn_decl | struct_decl | enum_decl | error_decl decl := ['pub'] (fn_decl | struct_decl | enum_decl | error_decl
| const_decl | global_decl) | const_decl | global_decl)
fn_decl := ['extern' string] ['interrupt'] 'fn' ident fn_decl := ['extern' string] [('interrupt' | 'interrupt_safe')] 'fn' ident
'(' [param (',' param)*] ')' ['->' type] (block | ';') '(' [param (',' param)*] ')' ['->' type] (block | ';')
param := ['comptime'] ident ':' type param := ['comptime'] ident ':' type
struct_decl := ['packed'] 'struct' ident '{' field* fn_decl* '}' struct_decl := ['packed'] 'struct' ident '{' field* fn_decl* '}'
@@ -237,11 +258,13 @@ enum_decl := 'enum' ident '{' variant (',' variant)* [','] '}'
variant := ident | ident '(' type ')' | ident '{' field* '}' variant := ident | ident '(' type ')' | ident '{' field* '}'
error_decl := 'error' ident '{' (ident '=' int ',')* '}' error_decl := 'error' ident '{' (ident '=' int ',')* '}'
const_decl := 'const' ident [':' type] '=' expr ';' const_decl := 'const' ident [':' type] '=' expr ';'
global_decl := ('static' | 'var') ident ':' type '=' expr ';' global_decl := 'static' ident ':' type '=' expr ';'
| 'var' ident ':' type '=' expr ';'
| 'shared' ['atomic'] 'var' ident ':' type '=' expr ';'
block := '{' stmt* '}' block := '{' stmt* '}'
stmt := 'let' ['mut'] ident [':' type] '=' expr ';' stmt := 'let' ident [':' type] '=' expr ';'
| 'var' ident ':' type ['=' expr] ';' | 'var' ident [':' type] ['=' expr] ';'
| 'const' ident [':' type] '=' expr ';' | 'const' ident [':' type] '=' expr ';'
| lvalue ('=' | '+=' | '-=' | '*=' | '/=' | '%=' | lvalue ('=' | '+=' | '-=' | '*=' | '/=' | '%='
| '&=' | '|=' | '^=' | '<<=' | '>>=') expr ';' | '&=' | '|=' | '^=' | '<<=' | '>>=') expr ';'
@@ -249,6 +272,7 @@ stmt := 'let' ['mut'] ident [':' type] '=' expr ';'
| 'return' [expr] ';' | 'break' ';' | 'continue' ';' | 'return' [expr] ';' | 'break' ';' | 'continue' ';'
| 'defer' block | 'defer' block
| 'unsafe' block | 'unsafe' block
| 'critical' block
| 'comptime' 'if' expr block ['else' (block | 'if' ...)] | 'comptime' 'if' expr block ['else' (block | 'if' ...)]
| 'asm' '{' asm_body '}' | 'asm' '{' asm_body '}'
| expr ';' | expr ';'
@@ -269,6 +293,7 @@ type := ident ['.' ident]
| '?' type | '!' type | ident '!' type | '?' type | '!' type | ident '!' type
| '^' type | '&' ['mut'] type | '*' type | '^' type | '&' ['mut'] type | '*' type
| 'far' ('^' | '*' | '&' ['mut']) type | 'far' ('^' | '*' | '&' ['mut']) type
| 'far' 'fn' '(' [type (',' type)*] ')' ['->' type]
| '[' expr ']' type | '[' ']' type | '[' expr ']' type | '[' ']' type
| 'fn' '(' [type (',' type)*] ')' ['->' type] | 'fn' '(' [type (',' type)*] ')' ['->' type]
| ident '(' type (',' type)* ')' // 제네릭 인스턴스 | ident '(' type (',' type)* ')' // 제네릭 인스턴스
@@ -278,20 +303,21 @@ type := ident ['.' ident]
``` ```
1 orelse, catch 1 orelse, catch
2 || 2 or
3 && 3 and
4 == != < <= > >= 4 == != < <= > >=
5 | ^ 5 | ^
6 & 6 &
7 << >> 7 << >>
8 + - +% -% 8 + - +% -%
9 * / % *% 9 * / % *%
10 단항: - ! ~ & &mut ^(주소아님) try 10 단항: - not ~ & &mut ^(주소아님) try
11 후위: .field .? .^ [i] [a..b] (args) as T 11 후위: .field .? .^ [i] [a..b] (args) as T
12 기본: literal, ident, '(' expr ')', struct_literal, @builtin(...) 12 기본: literal, ident, '(' expr ')', struct_literal, @builtin(...)
``` ```
- `&&`, `||`는 단축 평가. - `and`, `or`는 단축 평가한다. 호스트 C 방출에서는 각각 `&&`, `||`
매핑하며, 평가 순서와 단락 규칙은 Ferro 의미론을 그대로 유지한다.
- `as`는 후위 우선순위(단항보다 강함): `-x as i32``-(x as i32)`. - `as`는 후위 우선순위(단항보다 강함): `-x as i32``-(x as i32)`.
- 비교 연산 체이닝 금지(`a < b < c`는 에러). - 비교 연산 체이닝 금지(`a < b < c`는 에러).
@@ -307,9 +333,12 @@ type := ident ['.' ident]
@trap() -> never @unreachable() -> never (unsafe) @trap() -> never @unreachable() -> never (unsafe)
@line() @file() // 진단용 @line() @file() // 진단용
@print(fmt, ...) // stdout @print(fmt, ...) -> void // stdout, 쓰기 오류 무시
@fprint(w, fmt, ...) // 임의 Writer @fprint(w, fmt, ...) -> !void // 임의 Writer
@sprint(buf: []u8, fmt, ...) -> usize // 버퍼에 기록, 쓴 바이트 수 반환 @sprint(buf: []u8, fmt, ...) -> usize // 버퍼에 기록, 쓴 바이트 수 반환
@compile_error(msg) // comptime에서 항상 컴파일 에러
@as_far_fn(f) -> far fn() // bits16 전용 함수 포인터 변환
@call_far(p: far fn()) // bits16/unsafe 전용 호출
``` ```
### 6.3.1 포매팅 빌트인 ### 6.3.1 포매팅 빌트인
@@ -332,9 +361,21 @@ fmt.write_str(out, "\n");
- verb: `{}` 기본(정수/bool/char/str 자동), `{x}` 16진, `{c}` 문자, `{s}` 문자열/슬라이스, `{b}` 불린. `{{``{` 이스케이프. - verb: `{}` 기본(정수/bool/char/str 자동), `{x}` 16진, `{c}` 문자, `{s}` 문자열/슬라이스, `{b}` 불린. `{{``{` 이스케이프.
- `{}` 개수와 인자 개수 불일치 → 컴파일 에러. - `{}` 개수와 인자 개수 불일치 → 컴파일 에러.
- 인자 타입에 대응하는 `fmt.write_*` 함수가 없으면 컴파일 에러(메시지에 타입명 표시). - 인자 타입에 대응하는 `fmt.write_*` 함수가 없으면 컴파일 에러(메시지에 타입명 표시).
- 자릿수/폭/정렬 지정자는 v0.1에 없음. 필요하면 `fmt.write_int_pad`를 직접 호출. - 자릿수/폭/정렬 지정자는 v0.1.1에 없음. 필요하면 `fmt.write_int_pad`를 직접 호출.
- `@fprint`의 첫 인자는 `&mut io.Writer`(§10의 함수 포인터 struct). - `@fprint`의 첫 인자는 `&mut io.Writer`(§10의 함수 포인터 struct).
- 전개 결과의 모든 호출은 `!void`를 반환하므로 `@print` `!void`. 무시하려면 `_ = @print(...)`. - `@print`는 stdout에 기록하며 저수준 writer 오류를 삼키고 `void`를 반환한다.
따라서 `try @print(...)`는 컴파일 에러다.
- `@fprint`는 writer 오류를 전파하여 `!void`를 반환한다.
- `@sprint`는 버퍼가 찬 뒤의 출력이 잘리더라도 트랩하지 않고 기록된 바이트 수를
`usize`로 반환한다.
- 전개된 `fmt.write_*` 호출은 위 반환 규칙에 맞게 lower 단계에서 오류를
전파하거나 무시한다. `fmt.write_error``core.Error`의 이름/코드를 출력한다.
`@compile_error(msg)``msg`는 comptime 문자열이어야 하며, 평가되는 분기에서
항상 진단을 발생시킨다. `comptime if`의 제거되는 분기에서는 진단하지 않는다.
`@as_far_fn(f)``@call_far``bits16`에서만 허용된다. 전자는 함수 포인터를
`far fn()`으로 변환하고 후자는 `far fn()`을 호출한다. 둘 다 `unsafe { }` 안에서만
사용할 수 있으며, `bits32`에서는 컴파일 에러다.
### 6.4 예제 ### 6.4 예제
@@ -396,7 +437,7 @@ pub fn main() -> !void {
### 7.1 변수와 초기화 ### 7.1 변수와 초기화
- `let`은 불변, `let mut` 가변, `var` 타입 명시 필수인 가변 선언. - `let`은 불변, `var` 가변 선언이다. 두 형태 모두 초기값이 있으면 타입을 추론할 수 있다. `var x: T;``var x: T = undefined;`처럼 초기값이 없거나 `undefined`이면 타입 명시 필수.
- 모든 변수는 사용 전 초기화 필수(정적 검사). 명시적 미초기화는 `= undefined`(unsafe 아님, 단 읽기 전 쓰기 필수는 여전히 검사). - 모든 변수는 사용 전 초기화 필수(정적 검사). 명시적 미초기화는 `= undefined`(unsafe 아님, 단 읽기 전 쓰기 필수는 여전히 검사).
- 섀도잉 허용(같은 스코프에서 `let` 재선언). - 섀도잉 허용(같은 스코프에서 `let` 재선언).
@@ -415,7 +456,8 @@ pub fn main() -> !void {
- 기본: `bits32`는 cdecl, `bits16`은 타깃 C 컴파일러 기본. - 기본: `bits32`는 cdecl, `bits16`은 타깃 C 컴파일러 기본.
- `extern "c" fn name(...) -> T;` — 본문 없이 선언, C 심볼과 링크. 이름 맹글링 없음. 인자/반환에 `^T`, 슬라이스, 에러 유니온 사용 불가(`*T`, `usize`만). - `extern "c" fn name(...) -> T;` — 본문 없이 선언, C 심볼과 링크. 이름 맹글링 없음. 인자/반환에 `^T`, 슬라이스, 에러 유니온 사용 불가(`*T`, `usize`만).
- `interrupt fn name()` — 모든 레지스터 보존 + `iret`. 파라미터/반환 없음. 주소는 `@as_far_fn(name)`으로 획득. - `interrupt fn name()` — 모든 레지스터 보존 + `iret`. 파라미터/반환 없음. 주소는 `@as_far_fn(name)`으로 획득. 호출 제한과 공유 상태 규칙은 R10을 따른다.
- `interrupt_safe fn name(...)` — 인터럽트 문맥에서 호출 가능한 함수. ABI는 일반 함수와 같고 R10의 제한을 본문 검사로 만족해야 한다.
- 큰 struct(> 4바이트)는 숨은 포인터로 반환(C ABI 따름). - 큰 struct(> 4바이트)는 숨은 포인터로 반환(C ABI 따름).
### 7.4 검사와 트랩 ### 7.4 검사와 트랩
@@ -488,13 +530,15 @@ var xs: List(u8) = List(u8).new();
## 10. 표준 라이브러리 (최소 집합) ## 10. 표준 라이브러리 (최소 집합)
- **core**: `panic`, `set_panic_handler`, `Error`(기본 에러 집합), `assert`. - **core**: `panic`, `set_panic_handler`, `Error`(기본 에러 집합), `assert`.
- **mem**: `create(T) -> !^T`, `destroy(p)`, `alloc_slice(T, n) -> !^[]T`, `copy(dst, src)`, `set(dst, v)`, `Arena{ init, alloc, reset, drop }`. - **mem**: `create(T) -> !^T`, `destroy(p)`, `alloc_slice(T, n) -> !^[]T`, `replace(dst: &mut T, value: T) -> T`, `copy(dst, src)`, `set(dst, v)`, `Arena{ init, alloc, reset, drop }`. `replace`는 이전 값을 이동해 반환하고 새 값으로 자리를 초기화하며 재귀 구조의 반복 drop에도 사용한다.
- **str**: `eq`, `find`, `starts_with`, `split_at`, `parse_int`, `trim`, `to_cstr(buf, s)`, `from_cstr(p)`. - **str**: `eq`, `find`, `starts_with`, `split_at`, `parse_int`, `trim`, `to_cstr(buf, s)`, `from_cstr(p)`.
- **list**: `List(T)`. - **list**: `List(T)`.
- **map**: `Map(K, V)` (오픈 어드레싱, `K`는 정수/str). - **map**: `Map(K, V)` (오픈 어드레싱, `K`는 정수/str).
- **fmt**: `@print` 계열이 전개해 호출하는 저수준 함수 모음. - **fmt**: `@print` 계열이 전개해 호출하는 저수준 함수 모음.
`write_str(w, s)`, `write_int_i8/i16/i32/u8/u16/u32(w, v)`, `write_hex_u8/u16/u32(w, v)`, `write_str(w, s)`, `write_int_i8/i16/i32/u8/u16/u32(w, v)`, `write_hex_u8/u16/u32(w, v)`,
`write_char(w, c)`, `write_bool(w, b)`, `write_int_pad(w, v, width, pad)`. `write_char(w, c)`, `write_bool(w, b)`, `write_error(w, e)`,
`write_int_pad(w, v, width, pad)`. `write_error``--strip-error-names` 설정을
따르며, 모든 함수는 `!void`를 반환한다.
전부 `fn(w: &mut io.Writer, ...) -> !void` 시그니처. 사용자가 직접 호출해도 된다. 전부 `fn(w: &mut io.Writer, ...) -> !void` 시그니처. 사용자가 직접 호출해도 된다.
- **io**: - **io**:
- `File{ open, create, read, write, seek, size, close(=drop) }`, `stdin`, `stdout`, `stderr`. - `File{ open, create, read, write, seek, size, close(=drop) }`, `stdin`, `stdout`, `stderr`.
@@ -589,7 +633,9 @@ fec/
- **조건부 이동**: 이동 여부가 분기에 따라 다르면 `unsigned char fe_live_<var> = 1;` 플래그 삽입, drop 전에 검사. - **조건부 이동**: 이동 여부가 분기에 따라 다르면 `unsigned char fe_live_<var> = 1;` 플래그 삽입, drop 전에 검사.
- **`match`**: `switch (x.tag)`. 페이로드 바인딩은 지역 변수로 복사 또는 포인터. - **`match`**: `switch (x.tag)`. 페이로드 바인딩은 지역 변수로 복사 또는 포인터.
- **`asm`**: Intel 문법으로 고정 저장. Watcom/Borland는 그대로, gcc는 `__asm__(".intel_syntax noprefix\n" ...)`로 감싼다. - **`asm`**: Intel 문법으로 고정 저장. Watcom/Borland는 그대로, gcc는 `__asm__(".intel_syntax noprefix\n" ...)`로 감싼다.
- **`@print` 계열**: emit 단계에는 도달하지 않는다. lower 단계에서 이미 `fmt.write_*` 호출 나열로 전개되므로 emit_c는 일반 함수 호출로만 본다. 포맷 문자열 조각은 각각 static const 문자열 리터럴로 방출하고 동일 문자열은 중복 제거. - **`@print` 계열**: emit 단계에는 도달하지 않는다. lower 단계에서 이미 `fmt.write_*` 호출 나열로 전개되므로 emit_c는 일반 함수 호출로만 본다. `@print`는 각 호출의 오류 코드를 명시적으로 버리고 `void`가 되며, `@fprint`는 첫 오류를 전파하고, `@sprint`는 남은 버퍼 길이를 추적해 잘라 쓴 뒤 실제 길이를 반환한다. 포맷 문자열 조각은 각각 static const 문자열 리터럴로 방출하고 동일 문자열은 중복 제거.
- **논리 연산**: `and`, `or`, `not`은 각각 C의 `&&`, `||`, `!`로 방출한다. `and`와 `or`는 C의 시퀀스 포인트와 단축 평가를 그대로 사용한다.
- **임계 구역**: bits16의 `critical`은 진입 시 FLAGS를 저장한 뒤 `cli`하고 모든 이탈 경로에서 저장한 FLAGS를 복원한다. `shared atomic var`의 단일 접근도 같은 보존형 시퀀스를 사용하며 무조건 `sti`하지 않는다.
- **방출 순서**: typedef 전방선언 → struct 정의(의존 위상 정렬) → 전역 → 함수 프로토타입 → 함수 본문. - **방출 순서**: typedef 전방선언 → struct 정의(의존 위상 정렬) → 전역 → 함수 프로토타입 → 함수 본문.
- 유닛 하나당 `.c` 하나, `.fei`에서 필요한 부분은 `.h`로 생성. - 유닛 하나당 `.c` 하나, `.fei`에서 필요한 부분은 `.h`로 생성.
@@ -619,13 +665,13 @@ Uninit | Owned | Moved | MaybeMoved | Shared(n) | Exclusive
| M1 | lexer, parser, AST 덤프 | `--dump-ast`가 std 소스 전체를 파싱 | | M1 | lexer, parser, AST 덤프 | `--dump-ast`가 std 소스 전체를 파싱 |
| M2 | 타입 검사 + C 방출: 정수, 함수, if, while | bits32 hello world 실행 | | M2 | 타입 검사 + C 방출: 정수, 함수, if, while | bits32 hello world 실행 |
| M3 | struct, enum, match, 배열, 슬라이스, 경계 검사, str | 문자열 처리 예제 통과 | | M3 | struct, enum, match, 배열, 슬라이스, 경계 검사, str | 문자열 처리 예제 통과 |
| M4 | **`@print`/`@fprint`/`@sprint` 빌트인** (§6.3.1), `io.Writer` 함수 포인터 struct | 포맷 출력 동작, 인자 개수·타입 불일치가 컴파일 에러 | | M4 | **`@print`/`@fprint`/`@sprint` 빌트인** (§6.3.1), `io.Writer` 함수 포인터 struct | `@print`의 `void` 오류 삼킴, `@fprint`의 `!void` 전파, `@sprint`의 잘림/길이 동작 인자 개수·타입 불일치가 컴파일 에러 |
| M5 | `^T`, drop, defer, 이동 검사 | 누수/이중해제 테스트 통과 | | M5 | `^T`, drop, defer, 이동 검사 | 누수/이중해제 테스트 통과 |
| M6 | `&`, `&mut`, 배타성 검사 (own.c 전체) | R1~R8 실패 테스트 통과 | | M6 | `&`, `&mut`, 배타성 검사 (own.c 전체) | R1~R8 실패 테스트 통과 |
| M7 | `?T`, `E!T`, try/catch | io 유닛 동작 | | M7 | `?T`, `E!T`, try/catch | io 유닛 동작 |
| M8 | 유닛/import/.fei, 분리 컴파일, std 초안 | 다중 유닛 프로그램 빌드 | | M8 | 유닛/import/.fei, 분리 컴파일, std 초안 | 다중 유닛 프로그램 빌드 |
| M9 | **제네릭** (모노모피제이션) | `List(T)`, `Map(K,V)`를 Ferro로 재작성 | | M9 | **제네릭** (모노모피제이션) | `List(T)`, `Map(K,V)`를 Ferro로 재작성 |
| M10 | bits16 타깃: far, `@seg_ptr`, 메모리 모델, asm, interrupt fn | DOSBox에서 VGA 데모 실행 | | M10 | bits16 타깃: far, `@seg_ptr`, 메모리 모델, asm, interrupt fn, `shared`/`atomic`/`critical`/`interrupt_safe` | QEMU FreeDOS에서 자동화된 far 포인터·인터럽트 공유 상태 테스트 통과(VGA 데모는 수동/멀티모달 검증 대상이라 완료 게이트에서 제외) |
| M11 | 컴파일러 B를 Ferro로 작성, A로 빌드 | B가 M1~M10 테스트 통과 | | M11 | 컴파일러 B를 Ferro로 작성, A로 빌드 | B가 M1~M10 테스트 통과 |
| M12 | 셀프호스팅 fixpoint | B(B(B)) == B(B) 바이트 동일, A 폐기 | | M12 | 셀프호스팅 fixpoint | B(B(B)) == B(B) 바이트 동일, A 폐기 |
| M13 | 386 네이티브 백엔드 | gcc 없이 빌드, 컴파일 속도 10배 | | M13 | 386 네이티브 백엔드 | gcc 없이 빌드, 컴파일 속도 10배 |
@@ -644,13 +690,15 @@ Uninit | Owned | Moved | MaybeMoved | Shared(n) | Exclusive
tests/ tests/
pass/*.fe + *.expected 컴파일→실행→stdout 비교 pass/*.fe + *.expected 컴파일→실행→stdout 비교
fail/*.fe 첫 줄 "// ERROR:<line>:<메시지 일부>" fail/*.fe 첫 줄 "// ERROR:<line>:<메시지 일부>"
run16/*.fe bits16 빌드 후 DOSBox 실행, 출력 파일 비교 run16/*.fe bits16 빌드 후 QEMU FreeDOS 실행, 출력 파일 비교
boot/ A/B 출력 비교, fixpoint 검증 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(참조 반환 바인딩), R9(unsafe 밖 raw 역참조), match 완전성, 암묵 변환, 타입 불일치.
- 포매팅(§6.3.1) 전용 `fail/` 케이스: `{}` 개수 > 인자 개수, 인자 개수 > `{}` 개수, 미지원 verb(`{q}`), 런타임 값 포맷 문자열, 대응 `write_*` 없는 타입(예: struct), 닫히지 않은 `{`. - 포매팅(§6.3.1) 전용 `fail/` 케이스: `{}` 개수 > 인자 개수, 인자 개수 > `{}` 개수, 미지원 verb(`{q}`), 런타임 값 포맷 문자열, 대응 `write_*` 없는 타입(예: struct), 닫히지 않은 `{`, `try @print(...)`(void).
- 포매팅 `pass/` 케이스: 각 verb 1개 이상, `{{` 이스케이프, 인자 0개, `@sprint` 반환 길이 검증, `@fprint`를 `io.buf_writer`로 호출. - 포매팅 `pass/` 케이스: 각 verb 1개 이상, `{{` 이스케이프, 인자 0개, `@print`의 오류 삼킴, `@sprint` 반환 길이/잘림 검증, `@fprint`를 `io.buf_writer`로 호출.
- `@compile_error`, `@as_far_fn`, `@call_far`의 comptime/타깃/unsafe 제약과 `error.Name`의
`core.Error` 등록, 결정적 코드, `--strip-error-names`, `fmt.write_error`를 각각 pass/fail로 검증한다.
- 각 마일스톤은 해당 기능의 pass/fail 테스트와 함께 완료한다. - 각 마일스톤은 해당 기능의 pass/fail 테스트와 함께 완료한다.
- 회귀 실행: `make test` — 전 타깃 전 테스트. - 회귀 실행: `make test` — 전 타깃 전 테스트.
@@ -673,8 +721,9 @@ tests/
| 레이블 있는 break | 편의 | — | 플래그 변수 | | 레이블 있는 break | 편의 | — | 플래그 변수 |
| 슬라이스 패턴 매칭 | 편의 | — | 인덱스 비교 | | 슬라이스 패턴 매칭 | 편의 | — | 인덱스 비교 |
| `inline fn` | 편의 | — | C 방출 시 `static inline` | | `inline fn` | 편의 | — | C 방출 시 `static inline` |
| `must` 키워드 | 편의 | 실패를 트랩으로 바꾸는 문법 설탕일 뿐 핵심 의미론이 아님 | `expr catch @trap()` |
| 라이프타임 표기 (`'a`) | **구조적 불가** | 전역 분석 필요, R4를 풀어야 함 | R4 (2급 참조), 인덱스 핸들 | | 라이프타임 표기 (`'a`) | **구조적 불가** | 전역 분석 필요, R4를 풀어야 함 | R4 (2급 참조), 인덱스 핸들 |
| 스레드 | 구조적 불가 | DOS에 없음 | — | | 선점형 스레드 | 구조적 불가 | DOS 기본 실행 모델에 없고 함수 단위 소유권 모델을 넘어서는 동기화가 필요 | — |
| 매크로 / 전처리기 | **영구** | 도구 지원과 컴파일 속도 파괴 | `const`, `comptime if`, 제네릭, `@print` | | 매크로 / 전처리기 | **영구** | 도구 지원과 컴파일 속도 파괴 | `const`, `comptime if`, 제네릭, `@print` |
| 예외 | 영구 | 언와인딩 기반 시설 없음, 숨은 비용 | 에러 유니온 | | 예외 | 영구 | 언와인딩 기반 시설 없음, 숨은 비용 | 에러 유니온 |
| GC | 영구 | 결정적 비용 원칙 위반 | 소유권 + RAII + 아레나 | | GC | 영구 | 결정적 비용 원칙 위반 | 소유권 + RAII + 아레나 |