Merge branch 'master' into 'main'

Merge master into main

See merge request coolguy/doslang-mirror!1
This commit is contained in:
2026-08-17 17:59:59 +09:00
341 changed files with 19343 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
# yaml-language-server: $schema=https://json.schemastore.org/clangd.json
CompileFlags:
Add:
- -xc
- -std=c89
- -Ifec/src
+30
View File
@@ -0,0 +1,30 @@
# Legacy local QEMU state is no longer used, but may contain large user-owned images.
.qemu/
# Reproducible DOSBox-X/Open Watcom development cache and ephemeral runs
.dosboxx/
# Windows reserved-device artifact: `> nul` under Git Bash creates a real file.
# Committing it breaks checkout on Windows.
nul
# Local logs and editor/OS metadata
*.log
*.tmp
*.swp
*~
.DS_Store
Thumbs.db
# Python/uv environment and caches
.venv/
__pycache__/
*.py[cod]
# Node/tool caches
node_modules/
.npm/
.cache/
# host build output of the front end
.build/
+36
View File
@@ -0,0 +1,36 @@
---
description: "Fast read-only search agent for locating code. Use it to find files by pattern (eg. \"src/components/**/*.tsx\"), grep for symbols or keywords (eg. \"API endpoints\"), or answer \"where is X defined / which files reference Y.\" Do NOT use it for code review, design-doc auditing, cross-file consistency checks, or open-ended analysis — it reads excerpts rather than whole files and will miss content past its read window. When calling, specify search breadth: \"quick\" for a single targeted lookup, \"medium\" for moderate exploration, or \"very thorough\" to search across multiple locations and naming conventions."
display_name: Explore
tools: read, bash, grep, find, ls
model: gpt-5.6-luna
prompt_mode: replace
---
# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools.
You are STRICTLY PROHIBITED from:
- Creating new files
- Modifying existing files
- Deleting files
- Moving or copying files
- Creating temporary files anywhere, including /tmp
- Using redirect operators (>, >>, |) or heredocs to write to files
- Running ANY commands that change system state
Use Bash ONLY for read-only operations: ls, git status, git log, git diff, find, cat, head, tail.
# Tool Usage
- Use the find tool for file pattern matching (NOT the bash find command)
- Use the grep tool for content search (NOT bash grep/rg command)
- Use the read tool for reading files (NOT bash cat/head/tail)
- Use Bash ONLY for read-only operations
- Make independent tool calls in parallel for efficiency
- Adapt search approach based on thoroughness level specified
# Output
- Use absolute file paths in all references
- Report findings as regular messages
- Do not use emojis
- Be thorough and precise
+11
View File
@@ -0,0 +1,11 @@
{
"lsp": {
"clangd": {
"binary": {
"env": {
"INCLUDE": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.29.30133\\include;C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.19041.0\\ucrt;C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.19041.0\\shared;C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.19041.0\\um;C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.19041.0\\winrt"
}
}
}
}
}
+89
View File
@@ -0,0 +1,89 @@
# doslang 작업 규칙
DOS/Windows용 시스템 프로그래밍 언어 Ferro와 그 컴파일러 `fec`. 규범 문서는
`SPEC.md`이며 이 파일은 그것을 구현할 때의 작업 규칙만 다룬다.
## 문서 지도
| 파일 | 역할 |
|---|---|
| `SPEC.md` | 언어 명세. 유일한 규범 문서 |
| `IR.md` | 중간 표현. 프론트엔드와 기계 사이 |
| `TODO.md` | 남은 작업과 정해진 것. 언어 규칙은 `SPEC.md` 를 가리키기만 한다 |
| `fec/tests/*/README.md` | 각 fixture 디렉터리가 무엇을 검사하는지 |
| `audits/<날짜>-<주제>.md` | 그때 조사해보니 어땠는지. 불변 기록 |
## 파이프라인
```
.fe → fec → i386 asm → wasm → wlink → .exe
└ lexer parser resolve types own check (프론트엔드)
└ lower (IR)
└ x86 (백엔드)
```
`wasm``wlink`는 고정된 Open Watcom의 어셈블러와 링커다 (WebAssembly와 무관).
`SPEC.md` §1 철학 6: 링커와 오브젝트 포맷을 새로 만들지 않는다.
## 검증
```powershell
uv run python tests/run.py # 컴파일러가 프로그램에 대해 뭐라고 하는가
uv run python tests/exec.py # 컴파일된 프로그램이 실제로 무엇을 하는가
uv run python tests/build.py <프로그램.fe> # 하나만 빌드해서 돌려보기
```
- **두 스위트를 모두 통과해야 한다.** `run.py`만 보면 진단은 옳은데 코드가 안 나오는
상태를 놓친다. 보고만 되고 방출되지 않는 경계 검사가 그 예다.
- 완료하려는 기능을 직접 검사하는 fixture가 통과해야 한다. 테스트가 증명하지 않는
기능은 완료로 처리하지 않는다.
- 거부를 기대하는 fixture는 첫 줄에 `// ERROR:<줄>:<문구>` 마커를 둔다. 마커가 없으면
파일 이름이 기대값이 된다 — `bad`로 시작하면 거부, 아니면 통과.
- 실행 프로그램은 첫 줄들에 `// EXIT:<코드>`, `// OUTPUT:<문구>`, `// NOCHECKS:<코드>`
둔다. 마지막 것은 `--no-checks`로 다시 빌드해서 다른 결과를 요구한다.
- 툴체인은 `.dosboxx/watcom`에 고정되어 있고, 없으면 오류로 멈춘다.
## 함정
- 표준 라이브러리는 프로그램이 아니라 컴파일러 옆에 있다. `--std=<디렉터리>`
넘기며, 그 디렉터리 안에 `std/`가 있어야 한다.
- 유닛 경로의 각 segment는 소문자로 시작하고 `a-z0-9_`만, **최대 8자**다.
파일 경로와 정확히 대응한다 (`std.io``<std>/std/io.fe`).
- `extern "c" fn`은 이름을 그대로 쓴다. 나머지는 `fe_<유닛>_<이름>`으로 맹글링하며
어셈블러가 받지 않는 문자는 밑줄이 된다.
- 슬라이스 배치(포인터 다음 길이)와 wrapper 페이로드 위치는 각각 한 군데에만
적혀 있다. 두 군데가 되면 어긋난다.
## 파일 크기
**2,000 줄을 넘기지 않는다. 웬만하면 1,000 줄.** 넘어가면 나눈다. 나눌 때는
줄 범위로 자르고 -- 주제별로 묶는 것보다 정확하다, 한 줄도 잃거나 겹치지 않으니 --
공유하는 것은 비공개 헤더(`checkpri.h`, `lowerpri.h`)에 모은다.
## 조사 기록
한 번 조사하고 끝나는 것 -- 명세와 구현의 대조, 진단 증거 수집, 외부 감사 --
`audits/<날짜>-<주제>.md`에 남긴다. 날짜와 기준 커밋을 적는다. 조사에서
나온 **결론**은 `SPEC.md``TODO.md`로 옮기고, audit 자체는 그때 무엇을
봤는지의 기록으로 둔다.
본문은 고치지 않는다. 다만 해결되면 맨 위에 **해결 줄 하나**를 붙인다 --
어느 커밋에서 어떻게 정리됐는지. 그것 없이는 읽는 사람이 아직 살아있는
문제인지 알 수 없다.
계획 문서는 두지 않는다 -- 끝난 계획은 git log 다.
## 작업 흐름
- 명세 판단이 바뀌면 `SPEC.md`를 즉시 갱신한다. 구현이 명세와 다르면 둘 중 하나가
틀린 것이므로 그 자리에서 결론을 낸다.
- 언어 규칙을 완화하려거든 먼저 프로그램 쪽을 고쳐본다. 규칙이 진짜 언어를 못 쓰게
만들 때만 규칙을 건드리고, 무엇을 왜 바꿨는지 `TODO.md`에 남겨 사람이 판단하게
한다.
- 코드는 컴파일러 단계로 나눈다. 마일스톤 단위 분할은 폐기했다.
- 검증된 단위마다 커밋한다. primary 브랜치는 `master`다.
- `.dosboxx/`의 다운로드, 실행 작업공간, 로그는 커밋하지 않는다.
## 현재 상태
두 스위트의 통과 수가 현재 상태다. 남은 작업은 `TODO.md`에 있다.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+180
View File
@@ -0,0 +1,180 @@
# Ferro IR
`fec` 의 중간 표현. 이 문서는 IR 자체만 다룬다. 언어 규범은 `SPEC.md` 이고,
Ferro 의미론이 어떻게 이 형태로 펴지는지는 lowering 이 담당한다.
IR 을 두는 이유는 하나다. **검사가 끝난 AST 와 기계 사이의 거리가 너무 멀다.**
`try` 하나가 분기 두 개와 임시값 하나로 펴지고, `defer` 는 함수의 모든 이탈
경로에 복제되며, 배열 인덱스는 비교와 트랩을 낳는다. 그 전개를 명령어 선택과
같은 자리에서 하면 둘 다 읽을 수 없게 된다.
---
## 1. 형태
함수 단위다. 함수는 **기본 블록**의 목록이고, 블록은 **명령**의 목록과 하나의
**종결자**로 끝난다. 블록 중간에서 분기하지 않고, 종결자 뒤에 명령이 없다.
```
fn @okid.test() -> i32 {
$0: i32 ; 지역
b0:
%0 = const i32 7
store $0, %0
%1 = load i32 $0
ret %1
}
```
### 슬롯
| | | |
|---|---|---|
| `%n` | 임시값 | 한 번 정의되고 여러 번 쓰인다. 블록을 넘지 않는다 |
| `$n` | 지역 | 주소를 가진 스택 자리. 함수 진입 시 전부 잡는다 |
| `@name` | 전역·함수 | 링커가 보는 이름 |
임시값이 블록을 넘지 않으므로 φ 노드가 없다. 블록을 넘겨야 하는 값은 지역에
`store` 하고 다시 `load` 한다. 이것이 SSA 보다 코드를 조금 더 만들지만,
**레지스터 할당기를 블록 단위로 유지**해 준다. v0.1 에서는 그 교환이 맞다.
### 기계 타입
```
i8 i16 i32 ptr mem<N>
```
Ferro 타입은 여기서 사라진다. 구조체·배열·슬라이스·옵셔널·에러 유니온은 전부
`mem<N>` 이고, 필드 접근은 lowering 이 계산한 **바이트 오프셋**이다. IR 은
`Box(i32)` 라는 것을 모른다 — 크기 N 바이트짜리 메모리만 안다.
`ptr` 은 4바이트다. 세그먼트가 없으므로 포인터 종류도 하나뿐이다 (`SPEC.md` §2).
`bool``i8`, `char``i8`, `usize``i32` 다.
---
## 2. 명령
12개다.
```
%d = const <ty> <imm> 상수
%d = load <ty> <place> 메모리에서 읽는다
store <place>, %v 메모리에 쓴다
%d = addr <place> 주소를 뜬다
%d = <op> <ty> %a, %b 산술·비트 연산
%d = <cmp> <ty> %a, %b 비교. 결과는 i8
%d = cast <from> <to> %a 정수 폭 변환
%d = call @f(%a, ...) 호출
copy <place>, <place>, N N바이트 복사
```
`<op>``add sub mul div mod and or xor shl shr`
`<cmp>``eq ne lt le gt ge` (부호 있음/없음은 `<ty>` 가 정한다)
### place
`load`·`store`·`addr`·`copy` 의 피연산자다.
```
$n 지역
@name 전역
%p 포인터 임시값이 가리키는 곳
%p + <imm> 상수 오프셋. 필드 접근이 여기로 온다
```
인덱스처럼 오프셋이 상수가 아니면 lowering 이 주소를 먼저 계산한다.
```
; a[i] 는
%0 = load i32 $i
%1 = const i32 4 ; 원소 크기
%2 = mul i32 %0, %1
%3 = addr $a
%4 = add ptr %3, %2
%5 = load i32 %4
```
### 덩어리
**덩어리는 언제나 주소로 오간다.** 크기 임계값이 없다.
```
call @f(%p) ; f 가 mem<N> 을 받으면 %p 는 그 주소다
```
반환도 같다. `mem<N>` 을 반환하는 함수는 **첫 인자로 결과를 쓸 주소를 받는다**.
호출자가 자리를 잡고 넘긴다.
이 규약을 고른 이유는 단순해서만이 아니다. ISA 마다 다른 "구조체를 언제
레지스터로 넘기는가" 규칙을 통째로 피해간다. `extern "c"` 경계에서는 C ABI 로
변환해야 하며 그것은 백엔드의 일이다.
---
## 3. 종결자
4개다.
```
jmp b<n> 무조건 분기
br %c, b<t>, b<f> %c 가 0이 아니면 b<t>
ret [%v] 반환
trap <reason> <line> 중단
```
`trap``fe_trap(reason, UNIT_FILE, line)` 이 된다. `reason` 은 작은 정수이고
`UNIT_FILE` 은 **유닛당 하나뿐인 파일 이름 문자열**이다. 트랩 지점마다 문자열을
두면 실행 파일이 부풀기 때문이다.
`line` 은 컴파일 시점에 상수로 박힌다. 주소가 아니라 줄 번호를 남기는 방식은
당대에 흔했고, 심볼 테이블 없이 실패 지점을 말할 수 있는 가장 싼 방법이다.
reason 값:
| | |
|---|---|
| 0 | 배열·슬라이스 경계 |
| 1 | 정수 오버플로 |
| 2 | 0으로 나눔 |
| 3 | 도달할 수 없는 곳에 도달 (`@unreachable`) |
| 4 | 명시적 `@trap()` |
`--no-checks` 는 0·1·2 를 만드는 검사를 lowering 단계에서 생략한다. 3·4 는
소스에 쓰인 것이므로 남는다.
---
## 4. 함수와 전역
```
fn @unit.name(<ty>, ...) -> <ty> { ... }
extern fn @name(<ty>, ...) -> <ty>
global @unit.name : mem<N> = <초기값 바이트>
```
이름은 `유닛.이름` 이다. 제네릭 인스턴스도 여기서는 그냥 함수 하나다 —
모노모피제이션이 프론트엔드에서 끝나므로 **IR 에 제네릭이라는 개념이 없다.**
---
## 5. 이 IR 이 하지 않는 것
- **최적화 없음.** 상수 접기도, 죽은 코드 제거도 없다. 나중에 붙일 자리는 있다
- **φ 노드 없음.** 블록 간 값은 지역을 경유한다
- **타입 검사 없음.** 검사는 프론트엔드에서 끝났다. IR 이 잘못되었다면 lowering 의 버그다
- **예외·언와인딩 없음.** 에러는 값이고, `try` 는 분기다
---
## 6. 왜 이 크기인가
명령 12개와 종결자 4개는 **i386 으로 직접 번역할 수 있는 최소 집합**이다.
각각이 몇 개의 x86 명령으로 내려가고, 그 대응이 눈으로 확인된다. 이보다 높으면
백엔드가 IR 을 다시 해석해야 하고, 이보다 낮으면 lowering 이 기계에 가까워져서
다른 ISA 로 옮길 때 다시 써야 한다.
같은 이유로 이 집합은 m68k·ARM·MIPS·RV32 에도 그대로 내려간다. 평평한 주소
공간과 정수 연산만 쓰기 때문이다. 백엔드를 하나 더 만드는 비용은 명령 선택과
레지스터 할당이지 IR 재설계가 아니다.
+830
View File
@@ -0,0 +1,830 @@
# Ferro 언어 명세 v0.1.8
DOS용 시스템 프로그래밍 언어. C만큼 빠르고, 메모리 안전성을 함수 단위 지역 검사만으로 보장한다.
파일 확장자 `.fe`, 컴파일러 이름 `fec`. 별도의 인터페이스 파일은 없다 — 빌드 하나가 모든 유닛의 소스를 함께 읽는다.
이 문서는 Ferro 언어 명세만 다룬다. 컴파일러 구현 지시서와 표준 라이브러리 상세 명세는
별도 문서에서 다룬다. 명세 판단이 애매한 부분은 §1 철학과 §5 소유권 규칙을 기준으로 결정한다.
---
## 1. 설계 철학
1. **안전은 기본, 위험은 명시.** 기본 코드는 메모리 안전(널 역참조, 버퍼 오버런, use-after-free, 이중 해제 불가). 위험한 연산은 `unsafe {}` 블록 안에서만.
2. **전역 분석 금지.** 모든 검사(타입, 소유권, 참조)는 함수 하나만 보고 완결되어야 한다. 이 제약이 라이프타임 표기를 없애고, 진단을 위반 지점에 국소적으로 묶으며, 컴파일러를 작게 유지한다.
3. **숨은 비용 없음.** 힙 할당, 복사, 소멸자 호출, 형변환이 전부 소스에 보인다. GC 없음, 예외 없음, 암묵 변환 없음.
4. **읽히는 문법.** `이름: 타입` 순서, 좌→우 파싱, LL(1) 재귀하강으로 처리 가능.
5. **작게 시작.** 기능을 넣기 전에 뺄 이유를 먼저 찾는다. 뺀 것과 그 대체 수단은 §11에 기록한다.
6. **기존 도구체인 재사용.** 링커, `.OBJ`/`.LIB`/`.EXE` 포맷, DPMI 익스텐더를 새로 만들지 않는다.
---
## 2. 타깃
타깃은 하나다.
| | |
|---|---|
| CPU/모드 | i386 보호모드 플랫 |
| 실행 환경 | Windows 11, 그리고 DPMI 익스텐더 위의 DOS |
| 포인터 크기 | 4바이트 |
| 메모리 모델 | flat. 세그먼트 개념 없음 |
| `usize`/`isize` | 타깃의 포인터 폭 |
- 타깃이 하나이므로 타깃에 따라 갈라지는 소스는 v0.1에 없다.
- `usize`/`isize`는 **타깃의 포인터 폭**이며 특정 비트 수를 약속하지 않는다. 오늘
그것은 32비트지만 `u32`와 자동으로 변환되지 않는다. 폭을 언어 의미론으로
새어나가게 두지 않는 이 구분이, 나중에 다른 폭의 타깃을 여는 유일한 장치다.
- **세그먼트 주소 지정은 언어에 없다.** far 포인터는 x86 리얼모드에만 있는 개념이고,
평평한 주소 공간을 가진 다른 32비트 프로세서에는 대응물이 없다.
### 2.1 컴파일러 자신이 도는 곳
컴파일러 `fec`도 32비트 보호모드 플랫에서 돈다 — 호스트에서든, DOS에서든 DPMI
익스텐더 위에서다. 당대의 Open Watcom 컴파일러 자신이 그렇게 돌았다.
**8086 리얼모드는 이 명세의 범위 밖이다.** 640KB는 8086의 한계가 아니라 IBM PC가
1MiB 주소 공간의 위쪽 384KB를 하드웨어에 예약해서 생긴 것이고, 그 안에 컴파일러를
넣으려면 AST를 통째로 들지 않는 스트리밍 구조와 오버레이가 필요하다. 리얼모드
타깃이 필요해지면 그것은 백엔드와 `usize` 폭의 문제이지 언어 설계의 문제가 아니다.
---
## 3. 어휘 구조
- 식별자: `[A-Za-z_][A-Za-z0-9_]*`. 대소문자 구분.
- 주석: `//` 줄 끝까지, `/* */` **중첩 허용**.
- 정수 리터럴: `123`, `0xFF`, `0b1010`, `0o17`, 자릿수 구분 `1_000_000`. **타입은 문맥이 요구하는 정수 타입이고, 요구하는 것이 없으면 `i32`다.** 값이 그 타입의 범위를 벗어나면 컴파일 에러다 — `let b: u8 = 300;`은 44로 잘리는 것이 아니라 거부된다. 앞의 단항 `-`는 리터럴의 일부로 보아 `let n: i8 = -128;`은 허용하고 `let u: u8 = -1;`은 거부한다.
- 문자 리터럴: `'a'`, `'\n'`, `'\x41'` → 타입 `char`.
- 문자열 리터럴: `"abc"` → 타입 `str`. NUL 종료 아님. 이스케이프는 문자 리터럴과 동일. 인접 리터럴 자동 연결 없음.
- 불린: `true`, `false`. 옵셔널 널: `null`.
- 세미콜론 필수. 블록 중괄호 필수(단문 `if``{}` 필요).
**예약어:**
```
unit import pub fn struct packed enum error const static var let
if else while for in match return break continue defer
unsafe comptime asm try catch as extern
true false null undefined self Self type
and or not orelse
```
---
## 4. 타입 시스템
### 4.1 기본 타입
- 정수: `i8 i16 i32 u8 u16 u32 usize isize`
- `bool` (1바이트, 정수와 상호 변환 없음)
- `char` (`u8`과 크기 같지만 별개 타입). `char``u8` 사이의 저장·대입·비교에는
반드시 명시적인 `as` 변환이 필요하며, 리터럴에도 문맥 기반 암묵 변환을 적용하지 않는다.
- `void` (반환 타입으로만. 단, 역참조 불가능한 `*void`의 대상 타입은 허용, R9)
- `type` (comptime 파라미터와 type alias의 `const` 초기값에서만, §4.7·§9)
**정수 규칙:**
- 서로 다른 정수 타입 간 암묵 변환 없음. `as`로 명시.
- `as`는 절단/부호확장을 수행하며 값 손실을 검사하지 않는다.
- `+ - * / %`는 검사 빌드에서 오버플로 시 트랩. `+% -% *%`는 랩어라운드(항상 무검사).
- `/`, `%`의 0 나눗셈은 항상 트랩(검사 빌드 여부 무관, CPU가 트랩함).
- 시프트 `<< >>`: 우변은 `u8`. 시프트 양이 비트폭 이상이면 검사 빌드에서 트랩.
- 비트 연산 `& | ^ ~`는 같은 타입끼리만.
### 4.2 복합 타입
| 문법 | 의미 | 표현 |
|---|---|---|
| `[N]T` | 배열, 값 타입, N은 컴파일타임 상수 | `N * sizeof(T)` |
| `[]T` | 공유·읽기 전용 슬라이스 (참조성, R4 적용) | `(const ptr, len)` |
| `[]mut T` | 배타·쓰기 가능 슬라이스 (참조성, R4 적용) | `(ptr, len)` |
| `str` | 미리 선언된 `[]u8`의 type alias | `[]u8`과 동일 |
| `^[]T` | 소유 버퍼. 일반 `^T`와 구별되는 독립 소유 타입 | `(ptr, len)` |
| `^T` | 일반 소유 포인터 (힙, 단일 소유자) | 포인터 |
| `&T` | 공유 참조 | 포인터 |
| `&mut T` | 배타 참조 | 포인터 |
| `*T` | raw 포인터 (`unsafe`에서만 역참조) | 포인터 |
| `?T` | 옵셔널 | 널 표현 가능 타입은 크기 동일, 아니면 `(bool, T)` |
| `E!T` / `!T` | 에러 유니온 (`!T`는 기본 에러 집합) | `(u16 err, T val)` |
| `fn(A, B) -> R` | 함수 포인터 | 포인터 |
- **배열은 포인터로 붕괴하지 않는다.** 함수에 넘기려면 `arr[..]`로 슬라이스를 만들거나 `&arr` / `^[N]T`를 쓴다.
- 슬라이싱: `arr[..]`, `arr[a..b]`(반개구간, 경계 검사), `arr[a..]`, `slice[a..b]`. `let` 배열·공유 슬라이스에서는 `[]T`, `var` 배열·배타 슬라이스에서는 `[]mut T`가 생긴다.
- `[]mut T``[]T`로, `&mut T``&T`**호출 인자 위치에서만** 암묵 재대여할 수 있다. 이것은 호출 동안의 read-only view이며 원래 배타 대여는 원래 마지막 사용까지 유지된다. 일반 `let`/대입에는 이 암묵 약화를 적용하지 않는다. **반환 위치에서도 약화할 수 있다**`[]mut T``[]T`로, `&mut T``&T`로 반환하는 것은 R8이 이미 그 파생을 허용한 뒤에 가진 것보다 적게 넘기는 일이므로 새 별칭을 만들지 않는다. `&Self` 메서드가 자기가 소유한 것의 읽기 전용 뷰를 내주는 길이 이것뿐이다. 장기 shared borrow가 필요하면 root/place에서 명시적으로 새 `&` 또는 shared slice를 만들고 R6 검사를 받는다.
- **배타 대여를 호출에 넘기는 것은 이동이 아니라 그 호출 동안의 재대여다.** `&mut T``&mut T` 파라미터에, `[]mut T``[]mut T` 파라미터에 넘기면 호출이 끝날 때 돌려받는다. 호출이 도는 동안 호출자는 그 값에 손댈 수 없으므로 별칭이 생기지 않는다. 이것이 없으면 배타 파라미터를 다시 넘기는 일이 함수당 한 번만 가능해져서 `&mut`가 사실상 쓸 수 없게 된다.
- `^[]T`는 "슬라이스를 가리키는 포인터"가 아니라 길이를 함께 소유하는 독립 타입이다. R4의 일반 `^T` 대상 제한의 예외이며 `?^[]T`도 허용한다. `*[]T`/`*[]mut T`는 계속 금지한다. `mem.alloc_slice(T, n)`가 반환하고 drop 시 버퍼를 해제한다.
- `str`은 nominal 타입이 아니라 미리 선언된 `const str = []u8;` type alias다. UTF-8 검증을 보장하지 않으며 문자열 리터럴은 정적 읽기 전용 `[]u8`이다. 따라서 별도 변환 규칙이나 별도 표현은 없다.
### 4.3 구조체
```fe
pub struct Point {
x: i32,
y: i32,
pub fn new(x: i32, y: i32) -> Point { return Point{ x: x, y: y }; }
pub fn len2(self: &Self) -> i32 { return self.x*self.x + self.y*self.y; }
pub fn shift(self: &mut Self, dx: i32) { self.x += dx; }
}
```
- 리터럴: `Point{ x: 1, y: 2 }`. 모든 필드 명시 필수(기본값 없음).
- 메서드는 struct 블록 안에 정의. 첫 파라미터가 `self: Self | &Self | &mut Self`면 메서드.
- `x.f(y)``Point.f(x, y)`의 설탕. 자동 참조 취함(`x.shift(1)``Point.shift(&mut x, 1)`).
- `Self`는 자기 타입의 별칭.
- 필드 레이아웃은 선언 순서. 정렬은 자연 정렬. `packed struct`로 정렬 강제 해제.
- 소멸자: `fn drop(self: &mut Self)`를 정의하면 스코프 종료 시 자동 호출(§5 R3).
### 4.4 열거형 (태그드 유니온)
```fe
pub enum Shape {
Empty,
Circle(i32),
Rect{ w: i32, h: i32 },
}
```
- 표현: `struct { u8 tag; union {...} payload; }`. 배리언트 256개 초과 시 `u16` 태그.
- 페이로드 없는 배리언트만 있는 열거형은 정수처럼 취급되며 `as u8` 가능.
- 생성: `Shape.Circle(5)`, `Shape.Rect{ w: 3, h: 4 }`, `Shape.Empty`.
- 해체는 `match` 또는 `if let`으로만. 직접 필드 접근 불가.
### 4.5 옵셔널
```fe
var p: ?^Node = null;
if let Some(node) = p { node.value = 1; } // node: &mut Node (p가 mut일 때)
p.?.value = 1; // projection chain, 소유권 이동 없음
let v = mem.replace(&mut p, null).?; // 소유값을 실제로 꺼냄
```
- `?T`에서 T가 일반 `^T`, `&T`, `*T`, `fn`이면 널 포인터를 널 표현으로 사용한다. `?^[]T`는 빈 소유 버퍼와 null을 구별해야 하므로 `(bool, ^[]T)` 표현을 사용한다.
- `null`은 독립 runtime 타입이 없다. expected type으로 정확한 optional 또는 pointer-like 타입을 하나 결정할 수 있는 위치에서만 허용한다. `let p: ?^Node = null;``takes_optional(null);`은 허용하지만 `let p = null;`처럼 문맥이 없거나 둘 이상의 타입으로 해석 가능한 경우는 컴파일 에러다.
- 검사 없이 역참조 불가. `p.^`는 컴파일 에러, `p.?.^`가 필요.
- `.?`, `.field`, `[i]`는 place projection이다. projection chain은 값을 이동하지 않는다. Copy 값은 읽기에서 복사되며 비-Copy 값을 projection에서 꺼내는 것은 R7에 따라 금지한다. 실제 추출은 `mem.replace`를 사용한다.
- `orelse`는 Copy payload를 복사한다. 비-Copy optional 변수 자체에 적용하면 optional 전체를 이동하며, field/index projection의 비-Copy optional에는 직접 적용할 수 없다.
- `orelse`는 optional이 `Some`이면 우변을 평가하지 않고, `None`일 때만 우변을 평가하는 lazy 연산이다. 부수 효과·이동·대여도 실행되는 경로에만 적용한다.
- `Some``None``if let``match`의 패턴 위치에서만 옵셔널 해체를 의미하는 문맥 키워드다. 다른 위치에서는 일반 식별자이며 §3의 예약어가 아니다. 패턴은 place를 파괴적으로 추출하지 않는다. immutable place의 payload binding은 shared borrow/view, mutable place는 필요한 mutable borrow/view이고 Copy payload만 복사할 수 있다. non-Copy payload의 소유권을 꺼내려면 `mem.replace`가 필요하며 temporary optional만 자동 소유 추출하는 예외도 없다.
### 4.6 에러
```fe
pub error ParseError { // 사용자 nominal error 예시
InvalidDigit = 1,
Overflow = 2,
}
fn read_all(path: str) -> !^[]u8 { // 표준 io는 core.Error로 통일
var f = try io.open(path, io.Read); // 같은 core.Error면 즉시 반환
defer { f.close() catch @trap(); }
let n = f.size() catch |e| { return e; };
...
}
```
- `error` 선언은 `u16` 코드 집합. 코드 0은 "성공" 예약이라 사용할 수 없고, 한 선언 안에서 member 이름이나 숫자 code가 중복되면 컴파일 에러다. 서로 다른 nominal error 선언은 같은 숫자 code를 사용할 수 있지만 여전히 다른 타입이다.
- expected type이 `E!T`인 위치에서는 `T` 값은 success, `E` 값은 failure를 구성한다. 함수의 `return`도 선언된 반환 타입이 `E!T`이면 같은 규칙을 쓴다. 이는 일반 implicit conversion이 아니라 error-union 전용 contextual construction이며, `E1``E2` 또는 nominal error와 `core.Error` 사이의 자동 변환은 없다.
- `try`는 에러 유니온 반환 함수 안에서만 허용한다. 피연산자의 nominal error 타입은 현재 함수의 error 타입과 정확히 같아야 한다. 다르면 `catch`에서 명시적으로 매핑한다.
- `catch`는 현재 함수의 반환 타입과 무관하게 어디서든 에러를 그 자리에서 처리할 수 있다.
- `try e`: 에러면 현재 함수에서 즉시 반환한다.
- `e catch |x| { ... }`: 블록은 값을 만들 수 없다. 결과 타입이 `void`이면 정상적으로 끝까지 실행할 수 있고, 값 결과가 필요하면 `return`/`break`/`continue`로 탈출하거나 `@trap()`으로 끝나야 한다. 값이 필요하면 아래 짧은 형태를 쓴다. (언어에 블록 표현식을 도입하지 않기 위한 선택. §11 참조.)
- `e catch default_value`: 짧은 형태. 우변은 식이며 그 값이 결과가 된다.
- 짧은 `catch`의 우변과 block `catch`의 handler는 피연산자가 error일 때만 평가·실행한다. success이면 handler의 부수 효과·이동·대여가 발생하지 않는다.
- 서로 다른 error 타입 간 자동 변환 없음. `!T`(기본 에러 집합 `core.Error`)로 통일하거나 명시 매핑.
- `try``catch`도 field/index/optional projection에서 non-Copy payload를 숨게 이동시키지 않는다. projection에서 소유값을 추출해야 하면 먼저 `mem.replace`로 유효한 대체값을 남긴다.
- 에러는 값이다. 언와인딩, 스택 추적, 소멸자 이외의 자동 정리 없음.
- 실패를 복구하지 않고 트랩으로 바꾸려면 `expr catch @trap()`을 쓴다. v0.1에는 별도 `must` 키워드를 두지 않는다.
`error.Name`은 선언된 error 타입을 만들지 않고 기본 `core.Error`의 이름 있는
멤버를 참조하는 익명 에러 값이다. **드라이버가 emit 단계 이전에** 빌드에 든 모든
유닛에서 쓰인 이름을 모아 중복을 제거하고 이름의 바이트순으로 정렬하여 1부터
`u16` 코드를 부여한다. 따라서 서로 다른 유닛의 `error.Name`은 같은 값이고,
빌드 순서와 무관하게 결과가 결정적이다.
빌드 디렉터리 이력에 따라 번호가 달라지는 append-only 표는 금지한다.
번호는 빌드 전체를 봐야 정해지므로 유닛 하나만 따로 코드 생성까지 밀고 갈 수는
없다. 이름이 65,535개를 넘으면 컴파일 에러다. 명시적인 `error` 선언은 여전히 nominal
타입이며, 같은 멤버 이름이나 숫자 코드를 가진 다른 선언 및 `core.Error`와 자동
변환되지 않는다. `error.Name`의 타입은 `core.Error`이며 `core.Error!T` 또는
축약형 `!T`를 반환하는 함수에서만 직접 반환할 수 있다.
`--strip-error-names`를 사용하면 실행 파일과 런타임 오류 문자열에서 이름을
제거하지만 숫자 코드는 유지한다.
`fmt.fmt_error`는 이 정책에 따라 `core.Error` 값을 이름 또는 코드로 포맷한다.
### 4.7 타입 동등성과 alias
이름 기반(nominal). 필드가 같아도 다른 이름이면 다른 타입. `type` 값은 comptime 파라미터뿐 아니라 `const Alias = Type;`의 초기값에 허용하며, 이 선언은 새 nominal 타입이 아닌 완전 동일 alias를 만든다. 런타임 type 값은 없다. `str`은 이 규칙으로 미리 정의된 `[]u8` alias다(§4.2).
---
## 5. 소유권과 참조 — 핵심 규칙
이 절이 언어의 핵심이다. 모든 규칙은 **함수 하나만 보고** 검사된다.
**R1 (단일 소유자).** 모든 값의 소유자는 정확히 하나. 변수 대입, 함수 인자 전달, 반환은 **이동(move)**이다. 이동된 변수는 이후 사용 시 컴파일 에러.
**R2 (Copy 타입).** 다음은 이동 대신 복사된다: 정수, `bool`, `char`, raw 포인터 `*T`, 공유 참조 `&T`, 공유 슬라이스 `[]T`(`str` 포함), 함수 포인터. `?T`, `E!T`, `[N]T`, struct/enum은 모든 포함 값이 Copy이고 `drop`이 없을 때 재귀적으로 Copy다. `^T`, `^[]T`, `&mut T`, `[]mut T`는 Copy가 아니다.
**R3 (소멸자, RAII).** 일반 `^T``^[]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`(`str` 포함), `[]mut T`는 다음 위치에만 존재할 수 있다:
- 함수 파라미터
- 지역 변수 (`let`/`var`)
- 표현식 안의 임시값
다음은 **컴파일 에러**다:
- struct/enum 필드의 타입
- 배열/슬라이스의 원소 타입
- 함수 반환 타입 (예외: R8)
- 일반 `^T`, `*T`의 대상 타입
- 전역 변수의 타입
예외는 독립 소유 타입 `^[]T`/`?^[]T`와 문자열 리터럴로 초기화한 `const`/`static str`뿐이다. `^[]T`는 참조를 저장하지 않고 버퍼 자체를 소유한다. 이 제한이 라이프타임 표기 전체를 불필요하게 만든다.
**R5 (참조 수명).** 지역 참조 변수는 대상보다 오래 살 수 없다. R4 덕분에 대상은 항상 같은 함수의 지역 변수, 파라미터, 또는 `static` 전역이므로 스코프 중첩 확인만으로 검사된다. 가변 전역에 대한 대여는 R10이 금지한다.
**R6 (배타성).** `&mut x`가 살아있는 동안 `x`에 대한 다른 참조 생성, 직접 읽기/쓰기, 이동이 금지된다. `&x`(공유)는 여러 개 동시 가능하지만 그동안 `x`에 쓰기/이동 금지.
여기서 `x`는 변수가 아니라 **place**다. `p.a``p.b`는 서로 다른 place이므로 한쪽을 대여해도 다른 쪽은 그대로 읽고 쓸 수 있다. 대여가 필드 단위로 갈라지는 것은 루트 변수의 직속 필드 한 겹까지이며, 그 아래(`p.a.b`)와 인덱스(`arr[i]`)·역참조(`p.^`)는 전체를 대여한 것으로 본다. 전체를 대여하면 모든 필드와 충돌하고, 필드를 대여하면 전체 및 같은 필드와 충돌한다. 한 값에서 동시에 갈라둘 수 있는 필드 수에는 구현 상한이 있고, 넘으면 전체 대여로 되돌아간다 — 더 많이 보고할 뿐 놓치지는 않는다.
대여 상태는 **root local/parameter 와 그 직속 필드** 단위로 추적한다. `&mut xs[0]``&mut xs[1]`은 서로 다른 index라는 이유로 분리하지 않고 같은 root `xs`의 충돌 대여로 본다 — index는 상수가 아닐 수 있고, 그것을 따지는 것은 함수-local 상태 기계가 감당할 일이 아니다. 필드 이름은 상수라 그 문제가 없으므로 갈라진다.
```fe
var p = Pair{ a: 1, b: 2 };
let r = &mut p.a;
p.b = 3; // ok: p.b는 다른 place
p.a = 3; // 에러: p.a가 배타 대여 중
take(p); // 에러: 전체는 대여된 필드를 포함한다
r.^ = 4;
let a = &mut xs[0];
let b = &mut xs[1]; // 에러: 둘 다 root xs를 대여
```
참조의 생존 구간은 **참조 변수의 마지막 사용 지점까지**다. 그 이후에는 원본에 대한 접근·이동이 다시 허용된다. 조건부 흐름에서는 모든 경로의 마지막 사용 중 가장 나중 지점을 취한다. 임시 참조(`f(&x)`)는 그 문장 끝까지다. `defer` 블록에서 사용한 참조와 그 원본의 대여는 해당 defer가 실행되는 스코프 끝까지 연장한다.
호출 인자 위치의 `&mut T → &T`, `[]mut T → []T` 약화는 새 장기 공유 대여가 아니라 기존 배타 대여의 읽기 전용 재대여다. callee를 평가한 뒤 해당 인자를 평가하는 시점부터 호출이 끝날 때까지만 임시 재대여가 존재하고, 원래 배타 대여는 원래 마지막 사용까지 유지된다. 일반 `let`/대입에서는 암묵 약화를 허용하지 않으므로 `let s: &i32 = m;`(`m: &mut i32`)은 컴파일 에러다. 별도의 lifetime/coercion 시스템은 두지 않는다.
이 판정은 함수 지역 liveness 분석이며 함수 밖 정보를 쓰지 않으므로 §1.2를 위반하지 않는다.
```fe
var x: i32 = 0;
let r = &mut x;
r.^ = 1; // r의 마지막 사용
x += 1; // OK — 여기서 r의 대여는 이미 끝났다
```
**R7 (참조 무효화와 부분 이동).** 참조 대상이 이동되거나 재대입되면 그 참조는 이후 사용 시 에러. 이동 상태는 변수 단위로만 추적하므로 field/index/`.?` projection에서 비-Copy 소유값을 이동해 꺼내는 것은 금지한다(대여는 §5 R6대로 place 단위로 갈라지지만, 이동은 그렇지 않다). `mem.replace(&mut place, replacement)`로 유효한 대체값을 남기면서 꺼내야 한다. 배열의 선택적 소유 원소는 `?^T`로 두고 `mem.replace(&mut arr[i], null).?`로 꺼낸다. projection chain 자체(`p.?.^`, `s.field.x`)는 값을 소비하지 않는다.
이 금지에는 예외가 하나 있다. **타입 자신의 `drop` 안에서는 `self`의 projection에서 값을 꺼낼 수 있다.** 그 객체는 사라지는 중이고 `drop`이 돌아간 뒤에 그것을 읽을 수 있는 코드가 없으므로, R7이 막으려는 "뒤에 남은 반쪽짜리 값"이 생기지 않는다. 다른 함수에서는 예외가 없다.
**R8 (파생 반환).** 함수는 다음 두 경우에 한해 `&T`, `&mut T`, `[]T`, `[]mut T`와 이를 `?`로 감싼 타입을 반환할 수 있다.
**(a) 파라미터 파생.** 메서드는 파생 원본이 항상 참조성 `self`여야 한다. 다른 참조성 인자를 추가로 받을 수 있지만 반환값은 그 인자에서 파생될 수 없고 그 인자의 임시 대여는 문장 끝에 풀린다. 자유 함수는 참조성 파라미터(`&T`, `&mut T`, `[]T`, `[]mut T`)가 **정확히 하나**여야 한다. 두 경우 모두 반환값이 정해진 원본에서 파생됐음을 컴파일러가 함수 본문만 보고 확인한다. 파생은 슬라이싱, 인덱싱, 필드 접근, projection, `&`/`&mut` 취함과 다른 R8(a) 호출의 연쇄다. 반환의 가변성은 원본 이하여야 한다.
**(b) 정적 파생.** 반환값이 문자열 리터럴 또는 `static` 선언에서 파생된 경우. 이때는 참조성 파라미터가 없어도 된다.
참조성 반환의 provenance는 인터페이스에서 다음 둘로 정규화한다.
- `Static`: 문자열 리터럴 또는 `static`에서 파생되어 caller local borrow를 만들지 않는다.
- `Param(N)`: 시그니처로 정해진 하나의 참조성 parameter에서 파생된다. 메서드는 `Param(self)`만 허용하며 다른 참조성 인자에서 파생되면 에러다. 자유 함수는 기존 규칙대로 참조성 parameter가 정확히 하나여야 한다.
control-flow 합류는 `Static + Static → Static`, `Static + Param(N) → Param(N)`, `Param(N) + Param(N) → Param(N)`이다. 서로 다른 `Param` provenance가 합류하면 컴파일 에러다. `?&T`/`?[]T``null` 반환 경로는 caller borrow를 만들지 않는 경로이므로 static/null 경로와 `Param(N)` 경로가 합쳐지면 전체를 보수적으로 `Param(N)`으로 본다. 이 provenance는 함수 시그니처만 보고 결정할 수 있어야 한다.
호출 지점에서 `Param(N)` 결과는 **정해진 파생 원본을 대여한 것으로 취급**한다. 즉 결과를 지역 변수에 바인딩할 수 있으며, 그 대여가 사는 동안 원본에 R6·R7이 그대로 적용된다. `Static` 결과는 caller local borrow를 만들지 않는다.
```fe
let t = line.trim(); // 내장 alias 메서드 R8(a) // OK. line은 t의 대여 구간 동안 잠긴다
list.at_mut(0).x = 5; // &mut T, 배타 대여
let r = list.at(0); // &T, 공유 대여
list.push(1); // 에러: r이 list를 대여 중 (R6)
map.get_str(key); // ?&V: self에서만 파생, key는 문장 끝에 해제
pub fn name() -> str { return "main"; } // R8(b)
```
자유 함수에 참조성 파라미터가 둘 이상이면 어느 쪽에서 파생됐는지 시그니처만으로 결정되지 않으므로 참조성 반환을 할 수 없다. 그런 함수가 필요하면 메서드로 만들어 `self`를 원본으로 고정하거나 인덱스(`usize`)·핸들을 반환한다.
**R9 (unsafe).** `unsafe {}` 안에서만 허용: raw 포인터 역참조, `*T``^T`/`&T` 변환, `@ptr_cast`, `@volatile_*`, `@port_*`, `asm`, `*_unchecked` 함수. `*void`는 저장·비교·전달과 `@ptr_cast`에만 쓸 수 있고 직접 역참조할 수 없다. R1~R8은 `unsafe` 안에서도 그대로 유지된다. 특히 `unsafe`가 참조 반환·저장이나 대여 검사를 끄지 않으며, 프로그래머가 명시적으로 raw 포인터를 경유한 부분만 컴파일러의 메모리 안전 보장 밖에 놓인다.
**R10 (전역).** `static`은 불변이며 컴파일타임 상수 초기화만 가능하다. 일반 전역 `var`의 읽기와 쓰기는 안전하며 `unsafe`가 필요 없다.
- 전역에 대한 대여는 다음으로 제한한다. `static`(불변)은 `&`로 대여할 수 있다. 일반 전역 `var``&`·`&mut` 모두 대여할 수 없으며 직접 읽기와 쓰기만 허용한다. 전역 값을 참조로 넘겨야 하면 지역 변수로 복사한 뒤 대여한다.
- 이 제한의 근거는 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에 없다(§11).
**R11 (재귀·그래프 구조).** `^T`는 R4의 2급 참조가 아니므로 소유가 한 방향인 단방향 리스트와 트리는 필드에 저장할 수 있다. 반면 양방향 리스트·순환·일반 그래프는 역방향 필드에 `^T`를 두면 R1의 단일 소유권을 위반하고 `&T`를 두면 R4를 위반한다. 이런 구조는 아레나/배열이 값을 소유하고 `u16`/`u32` 인덱스 핸들이 간선을 나타내도록 구현한다. 표준 라이브러리 `mem.Arena`를 사용할 수 있으며, 핸들 역참조 때 세대 번호 또는 경계 검사를 사용해 해제된 항목 접근을 막아야 한다.
---
## 6. 문법
### 6.1 EBNF
```
unit := 'unit' unit_path ';' import* decl*
unit_path := ident ('.' ident)*
import := 'import' unit_path ['as' ident] ';'
decl := ['pub'] (fn_decl | struct_decl | enum_decl | error_decl
| const_decl | global_decl)
fn_decl := ['extern' string] 'fn' ident
'(' [param (',' param)*] ')' ['->' type] (block | ';')
param := ['comptime'] ident ':' type
generic_params := '(' ident (',' ident)* ')'
struct_decl := ['packed'] 'struct' ident [generic_params] '{' member* '}'
// field의 마지막 쉼표는 '}' 바로 앞에서 생략할 수 있다
member := ['pub'] (field | fn_decl)
field := ident ':' type ','
enum_decl := 'enum' ident [generic_params] '{' variant (',' variant)* [','] '}'
variant := ident | ident '(' type ')' | ident '{' vfield* '}'
vfield := ident ':' type ','
error_decl := 'error' ident '{' ident '=' int_literal
(',' ident '=' int_literal)* [','] '}'
const_decl := 'const' ident [':' type] '=' expr ';'
global_decl := 'static' ident ':' type '=' expr ';'
| 'var' ident ':' type '=' expr ';'
block := '{' stmt* '}'
stmt := 'let' ident [':' type] '=' expr ';'
| 'var' ident [':' type] ['=' expr] ';'
| 'const' ident [':' type] '=' expr ';'
| lvalue ('=' | '+=' | '-=' | '*=' | '/=' | '%='
| '&=' | '|=' | '^=' | '<<=' | '>>=') expr ';'
| if_stmt | while_stmt | for_stmt | match_stmt
| 'return' [expr] ';' | 'break' ';' | 'continue' ';'
| 'defer' block
| 'unsafe' block
| 'comptime' 'if' expr block ['else' (block | 'if' ...)]
| 'asm' '{' asm_body '}'
| expr ';'
if_stmt := 'if' (expr | 'let' pattern '=' expr) block
['else' (block | if_stmt)]
while_stmt := 'while' expr block
for_stmt := 'for' ident [',' ident] 'in' for_source block
for_source := expr ['..' expr]
match_stmt := 'match' expr '{' arm+ '}'
arm := pattern '=>' (expr ';' | block)
pattern := ident // 배리언트, 페이로드 없음
| ident '(' ident ')' // 튜플형 배리언트 바인딩
| ident '{' ident (',' ident)* '}' // 필드형 배리언트 바인딩
| 'Some' '(' ident ')' | 'None'
| int_literal | char_literal | 'true' | 'false' | '_'
type_name := [ident '.'] ident // [binding '.'] Name
type := type_name
| '?' type | '!' type | type_name '!' type
| '^' type | '&' ['mut'] type | '*' type
| '[' expr ']' type | '[' ']' ['mut'] type
| 'fn' '(' [type (',' type)*] ')' ['->' type]
| type_name '(' type (',' type)* ')' // 제네릭 인스턴스
catch_expr := expr 'catch' expr | expr 'catch' '|' ident '|' block
orelse_expr := expr 'orelse' expr
```
`member``pub`은 필드와 메서드 모두에 개별로 붙는다(§8). 필드와 메서드는 순서를
섞어 쓸 수 있다. `catch`의 두 형태는 서로 다른 일을 한다: 값을 주는 짧은 형태와,
에러를 받아 빠져나가는 블록 형태다. 블록은 값을 만들지 않으므로(§11) 바인딩이
있는 쪽만 블록을 받는다(§4.6). `unit_path` segment의 lexical 제한과 source path
대응은 §8.1이 규정한다.
타입 이름은 `[binding '.'] Name`이다. `import`는 unit path의 **마지막 segment**를
바인딩하므로(§8.2) 점이 둘 이상인 타입 이름은 만들어질 수 없다. `unit_path`
자체는 `import``unit` 선언에서만 쓴다.
전역 `static`/`var`는 타입을 적는다. 다른 유닛이 읽는 링커 심볼이라 초기값의
생김새에 타입을 맡기면 그쪽이 보는 것이 달라진다. `const`는 추론한다.
### 6.2 표현식 우선순위 (낮음 → 높음)
표현식 문법은 다음 우선순위 표가 규범이다. 각 단계는 명시가 없으면 좌결합 이항
연산으로 전개하며, 단항·후위·기본 단계에 나열된 형태가 그대로 프로덕션이 된다.
`catch``orelse`의 구체적 형태는 §6.1을 따른다.
```
1 orelse, catch
2 or
3 and
4 == != < <= > >=
5 |
6 ^
7 &
8 << >>
9 + - +% -%
10 * / % *%
11 단항: - not ~ & &mut try
12 후위: .field .? .^ [i] [a..b] (args) as T
13 기본: literal, ident, '(' expr ')', struct_literal, @builtin(...)
```
- `and`, `or`는 단축 평가한다. 좌변이 답을 정하면 우변을 평가하지 않는다.
- `orelse``catch`도 lazy다. 좌변이 각각 `Some`/success이면 우변 또는 handler를
평가하지 않는다(§4.5·§4.6).
- `as`는 후위 우선순위(단항보다 강함)지만 단항 연산자 바로 뒤에 `as`가 나타나면 모호한 비용을 숨기지 않도록 괄호를 강제한다. `(-x) as u32``-(x as u32)`는 허용하고 `-x as u32`는 컴파일 에러다.
- `|`, `^`, `&`는 서로 다른 단계다. C와 같은 순서이며, 한 단계로 합치면
`a | b ^ c`가 좌결합으로 `(a | b) ^ c`가 되어 C에서 온 사람을 속인다.
- `..`는 일반 표현식 연산자가 아니며 `for` 헤더에서만 쓸 수 있다.
- 비교 연산 체이닝 금지(`a < b < c`는 에러).
- `.field`, `[i]`, `[a..b]`, 메서드 호출은 `&`, `&mut`, `^`를 필요한 만큼 자동 projection한다. 값 자체의 역참조는 `.^`가 필요하며 raw `*T`와 optional `?T`는 자동 역참조하지 않는다.
- `x.f(args)`는 메서드를 우선 탐색한다. 함수 포인터 필드를 호출하려면 `(x.f)(args)`로 쓴다.
### 6.3 빌트인
```
@size_of(T) -> usize @align_of(T) -> usize
@target -> comptime str
@ptr_cast(T, p) -> *T (unsafe)
@port_in8(p) @port_in16(p) @port_out8(p,v) @port_out16(p,v) (unsafe)
@volatile_load(p) @volatile_store(p, v) (unsafe)
@trap() -> never @unreachable() -> never (unsafe)
@line() @file() // 진단용
@print(fmt, ...) -> void // stdout, 쓰기 오류 무시
@fprint(w, fmt, ...) -> !void // 임의 Writer
@sprint(buf: []mut u8, fmt, ...) -> usize // 버퍼에 기록, 쓴 바이트 수 반환
@compile_error(msg) // comptime에서 항상 컴파일 에러
```
### 6.3.1 포매팅 빌트인
가변 인자를 언어에 도입하지 않는다. `@print` 계열은 **컴파일 단계에서 여러 호출로 전개되는 빌트인**이다.
```fe
@print("x={} y={x} name={s}\n", a, b, s);
```
→ lower 단계에서 개념적으로 다음처럼 전개한다. 아래 `io``fmt`는 각각 canonical
`std.io`, `std.fmt` 유닛을 가리킨다.
```
io.write(out, "x=");
var t1: [12]u8 = undefined; io.write(out, fmt.fmt_int_i32(t1[..], a));
io.write(out, " y=");
var t2: [8]u8 = undefined; io.write(out, fmt.fmt_hex_u16(t2[..], b));
io.write(out, " name="); io.write(out, s); io.write(out, "\n");
```
`fmt.fmt_*``[]mut u8` 임시 버퍼에 쓰고 그 버퍼에서 파생된 `str`을 반환하는 순수 함수다. R8(a)의 원본이 하나이므로 별도 lifetime 표기가 필요 없다. 포매팅과 sink를 분리해 `@print`, `@fprint`, `@sprint`가 같은 변환 함수 한 벌을 사용한다.
규칙:
- 포맷 문자열은 **컴파일타임 문자열 리터럴 또는 `const`만**. 런타임 값이면 에러.
- verb: `{}` 기본(정수/bool/char/str 자동), `{x}` 16진, `{c}` 문자, `{s}` 문자열/슬라이스, `{b}` 불린. `{{``{` 이스케이프.
- `{}` 개수와 인자 개수 불일치 → 컴파일 에러.
- 인자 타입에 대응하는 `fmt.fmt_*` 함수가 없으면 컴파일 에러(메시지에 타입명 표시).
- 자릿수/폭/정렬 지정자는 v0.1에 없음. 필요하면 `fmt.fmt_int_pad`를 직접 호출.
- `@fprint`의 첫 인자는 Copy 핸들 `io.Writer`(§10)다.
- `@print``io.Writer.Stdout`에 기록하며 저수준 writer 오류를 삼키고 `void`를 반환한다.
따라서 `try @print(...)`는 컴파일 에러다.
- `@fprint`는 writer 오류를 전파하여 `!void`를 반환한다.
- `@sprint`는 같은 `fmt.fmt_*` 결과를 대상 `[]mut u8``mem.copy`로 이어 붙인다. 버퍼가 찬 뒤의 출력이 잘리더라도 트랩하지 않고 기록된 바이트 수를 `usize`로 반환한다.
- 전개된 `io.write` 호출은 위 반환 규칙에 맞게 lower 단계에서 오류를 전파하거나 무시한다. `fmt.fmt_error``core.Error`의 이름/코드를 포맷한다.
`@compile_error(msg)``msg`는 comptime 문자열이어야 하며, 평가되는 분기에서
항상 진단을 발생시킨다. `comptime if`의 제거되는 분기에서는 진단하지 않는다.
### 6.4 예제
```fe
unit frame;
const WIDTH: usize = 320;
const HEIGHT: usize = 200;
pub struct Buffer {
pixels: []mut u8,
pub fn put(self: &mut Self, x: usize, y: usize, c: u8) -> void {
if x >= WIDTH or y >= HEIGHT { return; }
self.pixels[y * WIDTH + x] = c;
}
}
```
```fe
unit main;
import std.io;
fn count_lines(path: str) -> !usize {
var f = try io.open(path, io.Read);
defer { f.close() catch @trap(); }
var buf: [256]u8 = undefined;
var n: usize = 0;
while true {
let got = try f.read(buf[..]);
if got == 0 { break; }
for c in buf[0..got] {
if c.^ == '\n' as u8 { n += 1; }
}
}
return n;
}
pub fn main() -> !void {
let n = count_lines("data.txt") catch |e| {
@print("failed: {}\n", e);
return e;
};
@print("{}\n", n);
}
```
---
## 7. 의미론 세부
### 7.1 변수와 초기화
- `let`은 불변, `var`는 가변 선언이다. 두 형태 모두 초기값이 있으면 타입을 추론할 수 있다. `var x: T;``var x: T = undefined;`처럼 초기값이 없거나 `undefined`이면 타입 명시가 필수다.
- `&mut x`, mutable slice 생성과 `&mut Self` 메서드 호출은 `var` place에서만 가능하다. `let``^T`를 보유해도 그 대상을 안전 코드에서 변경할 수 없다. by-value `self: Self`는 소비 메서드 안에서 자신의 필드를 무효 상태로 바꿀 수 있는 가변 local owner로 취급한다.
- 모든 변수는 사용 전 초기화 필수(정적 검사). 명시적 미초기화는 `= undefined`(unsafe 아님, 단 읽기 전 쓰기 필수는 여전히 검사).
- 섀도잉 허용(같은 스코프에서 `let` 재선언).
- **전역 `const`/`static`/`var`의 초기값은 컴파일 시점에 알 수 있어야 한다.** 저장소가 이미지에 들어가므로 초기값이 실행될 순간이 없다. 리터럴, 다른 `const`, 열거형 배리언트, `error.Name`, 그리고 그것들에 대한 연산과 캐스트·집합체 리터럴까지가 허용되며 함수 호출은 허용되지 않는다. 실행 시점에 계산해야 하는 값은 `main`에서 만든다.
### 7.2 제어 흐름
- `for x in slice`: `x``&T`(`[]mut T`이면 `&mut T`). 값 접근은 `x.^`.
- `for i, x in slice`: `i: usize`.
- `for i in a..b`: 정수 범위.
- 이 루프 형태들은 경계 검사를 생략한다(컴파일러가 안전을 보장).
- `while``bool` 조건만.
- `match`**완전성 검사**. 모든 배리언트를 다루거나 `_` 필요.
- `if`/`while`/`for`/`match`/`comptime if` 헤더 바로 뒤의 `{`는 항상 해당 제어 흐름의
본문 또는 arm 블록을 시작한다. 따라서 구조체 초기화식을 헤더의 최상위 식으로 직접
쓸 때는 `match (Point{ x: 1, y: 2 }) { ... }`처럼 괄호로 감싸 구조체 초기화의 `{`
명시한다. 괄호 안의 구조체 초기화는 일반 식 규칙을 따른다.
- `break`/`continue`는 가장 안쪽 루프에만 적용(레이블 없음).
- `defer block`은 스코프 종료 시 역순 실행. 소멸자와 함께 선언 역순으로 병합 실행. `return`/`break`/에러 전파 경로에서도 실행.
### 7.3 함수 호출 규약
- 기본: cdecl.
- `extern "c" fn name(...) -> T;` — 본문 없이 선언, C 심볼과 링크. 이름 맹글링 없음. 인자/반환에 `^T`, 슬라이스, 에러 유니온 사용 불가(`*T`, `usize`만).
- 큰 struct(> 4바이트)는 숨은 포인터로 반환(C ABI 따름).
### 7.4 검사와 트랩
트랩 발생 조건: 배열/슬라이스 경계 초과, 정수 오버플로, 0 나눗셈, `?T``.?` 실패, `@trap()`.
동작: `core.panic(msg: str, file: str, line: u32)` 호출 → 등록된 `sys.on_exit(fn)` 정리 함수를 역순 호출 → 메시지 출력 → `sys.exit(3)`. 사용자가 `core.set_panic_handler`로 교체 가능. 일반 panic unwind나 defer 실행은 없지만 프로세스 종료 전에 반드시 복원해야 하는 자원은 allocation 없는 고정 크기 `on_exit` registry에 등록한다.
`--no-checks` 빌드에서 제거되는 것: 경계 검사, 오버플로 검사, `.?` 검사. **오버플로 검사가 없을 때 `+ - * /`의 결과는 랩어라운드로 정의된다** — 타깃의 정수 연산이 그대로 하는 일이며, 미정의 동작으로 두지 않는다. 즉 `--no-checks`에서 `a + b``a +% b`와 같은 값을 낸다.
**절대 제거되지 않는 것:** 소유권/참조 검사, 옵셔널 타입 검사, `match` 완전성 — 전부 컴파일타임이므로.
### 7.5 comptime
- `const` 선언의 초기값은 컴파일타임 평가(정수 연산, `@size_of`, 다른 const).
- `comptime if`는 평가되지 않는 분기를 **파싱은 하되 타입 검사/코드 생성하지 않는다**(타깃별 분기용).
- 함수의 `comptime` 파라미터는 §9 제네릭.
- 재귀 평가 깊이 제한 256, 초과 시 에러.
### 7.6 표현식 평가 순서
- 일반 표현식의 평가 순서는 소스의 왼쪽에서 오른쪽이다.
- 함수·메서드 호출은 callee를 먼저 평가하고 인자를 소스 순서대로 왼쪽에서 오른쪽으로 평가한다. 자동 `self` projection도 callee 평가의 일부다.
- 이항 연산자는 왼쪽 operand를 먼저, 오른쪽 operand를 나중에 평가한다.
- `and``or`는 왼쪽 operand로 결과가 정해지면 오른쪽을 평가하지 않는다. `orelse``catch`도 §4.5·§4.6에 따라 우변/handler가 필요한 경로에서만 평가한다.
- 이 순서는 부수 효과뿐 아니라 move, borrow의 시작·마지막 사용, `try` 전파와 defer/drop cleanup 순서를 결정한다.
---
### 7.7 작은 규칙들
구현자가 임의로 정하면 갈라지는 것들. 각각 한 줄이면 끝나므로 여기 모아 둔다.
| | |
|---|---|
| `match` | **enum 값에만 쓴다.** 정수와 `char`에는 쓸 수 없으므로 완전성 검사가 배리언트 목록 하나로 정해진다. 정수 분기는 `if`/`else if`로 쓴다 |
| enum payload | `^T`처럼 소유하는 타입을 담을 수 있다. 다만 v0.1은 **활성 배리언트의 payload를 자동으로 놓아주지 않는다** — 담았다면 꺼내서 직접 놓아야 한다 |
| `for x in xs` | 순회는 `xs`**순회 동안 대여한다**. `x`가 그 안을 가리키는 참조이므로 몸통에서 `xs`에 쓰는 것은 R6 위반이다. 읽는 것은 공유 순회에서 허용된다 |
| `defer` 안의 `return` | **컴파일 에러.** 지연 블록은 함수가 무엇을 반환할지 이미 정한 뒤 스코프 정리 중에 돈다 |
| `undefined` 배열 | 초기화 추적은 **변수 단위이며 원소 단위가 아니다.** `undefined`로 선언한 배열은 선언 시점부터 쓰기 가능하고, 읽기 전에 무엇을 채웠는지는 검사하지 않는다. 슬라이스로 넘겨 채우는 것이 의도된 사용법이다 |
| 다른 유닛의 struct 리터럴 | 모든 필드를 명시해야 하므로 **`pub`이 아닌 필드가 하나라도 있으면 밖에서 리터럴을 쓸 수 없다.** 생성자 함수를 두어야 한다 |
| by-value `self` | `self`를 값으로 받아도 필드를 꺼내는 것은 R7 그대로 `mem.replace`가 필요하다. 예외는 자기 `drop` 안뿐이다 |
## 8. 유닛
파일 하나가 유닛 하나다. 유닛의 canonical identity는 fully-qualified dotted unit path이며
모든 cross-unit 타입·선언·제네릭 identity에서 같은 이름을 사용한다.
```fe
unit game.main;
import game.render;
import tinyjson.parse;
import net.http as http;
import std.io;
```
import는 항상 유닛 전체를 가져오며 member는 local unit binding으로 한정해 접근한다.
기본 binding은 마지막 segment이므로 `import tinyjson.parse;` 뒤에는
`parse.read(...)`, `import std.io;` 뒤에는 `io.write(...)`를 쓴다. `as`가 있으면 그
alias가 binding이다.
`std` 최상위 namespace는 compiler-reserved이며 user unit은 선언할 수 없다. 표준 유닛은
`import std.io;`, `import std.mem;`, `import std.fmt;`, `import std.sys;`처럼 가져온다.
`str`은 계속 built-in `[]u8` alias와 alias-method namespace이며 import unit이 아니다.
v0.1은 relative import(`.foo`, `..foo`), glob/selective import, `pub import` re-export,
package-private/friend visibility, package manager를 지원하지 않는다.
### 8.1 unit 이름과 source path
unit path의 각 segment는 ASCII lowercase `a`~`z`로 시작하고 이후에는 `a`~`z`,
`0`~`9`, `_`만 쓸 수 있으며 최대 8자다. 일반 Ferro identifier는 계속 case-sensitive이고
이 제한은 unit path에만 적용한다. `game.main`, `tinyjson.parse`는 허용하지만
`TinyJson.Parse`, `very_long_library_name`은 unit path로 허용하지 않는다. 이 규칙은
FAT/DOS 8.3과 case-sensitive host에서 같은 source가 같은 유닛으로 해석되게 한다.
unit path는 import root 아래의 상대 source path와 정확히 대응한다. canonical identity는
항상 dotted path이고 실제 path separator만 host/DOS에 맞게 바꾼다.
```
tinyjson.parse -> tinyjson/parse.fe
game.world.map -> game/world/map.fe
```
### 8.2 binding과 visibility
한 unit에서 import binding은 다른 unit-scope declaration/import binding과 충돌할 수 없다.
`import foo.net; import bar.net;`은 둘 다 `net`을 만들므로 에러이며 두 번째를
`import bar.net as bar_net;`처럼 alias해야 한다. alias는 일반 Ferro identifier다.
visibility는 private과 `pub` 두 단계뿐이다. private 선언은 같은 unit에서만 보이고,
`pub` 선언과 개별 `pub` field/method만 import한 모든 caller에서 보인다. dotted prefix는
권한이 아니므로 `game.foo``game.bar`는 서로의 private 선언에 접근할 수 없다. public
function parameter/return, public field 등 외부 signature에 나타나는 nominal type은
importer가 이름을 해석할 수 있어야 하며 private nominal type을 public API에 노출하면
컴파일 에러다.
### 8.3 순환과 canonical identity
순환 import는 컴파일 에러다. fully-qualified dotted unit path와 선언 이름이 nominal
identity의 기준이므로 `tinyjson.value.Value``tinyjson.value.Box`처럼 표시한다. nominal
struct/enum/error는 defining unit + declaration name으로 구별되고 type alias는 새 nominal
identity를 만들지 않는다.
---
## 9. 제네릭
`comptime` type 파라미터 기반 모노모피제이션. v0.1의 user-defined generic parameter는
`type`만 지원한다.
타입 파라미터는 본문에서 사용되지 않아도 된다. `struct Handle(T) { raw: u32 }`
정상이며 `Handle(Node)``Handle(Kind)`는 서로 다른 nominal 인스턴스다 — 파라미터가
하는 일이 저장소를 서술하는 것이 아니라 두 인스턴스를 갈라놓는 것뿐인 경우이고,
typed handle이 정확히 그 모양이다. 미사용 파라미터에 경고를 내지 않는다.
```fe
pub struct List(T) {
items: ^[]T,
len: usize,
pub fn new() -> List(T) { ... }
pub fn push(self: &mut Self, v: T) -> !void { ... }
pub fn at(self: &Self, i: usize) -> &T { ... } // R8 공유
pub fn at_mut(self: &mut Self, i: usize) -> &mut T { ... }
pub fn drop(self: &mut Self) { ... }
}
fn max(comptime T: type, a: T, b: T) -> T {
if a > b { return a; }
return b;
}
let m = max(i32, 3, 7);
var xs: List(u8) = List(u8).new();
```
- `fn id(comptime T: type, x: T) -> T`를 기본형으로 하며 `struct Box(T)`
`enum Maybe(T)``T``comptime T: type`의 shorthand다. `comptime N: usize`,
comptime string/bool 등 user value generic은 지원하지 않는다. compiler builtin의 기존
comptime value는 user generic parameter가 아니다.
- generic type argument는 항상 명시한다. `id(i32, 3)`은 허용하지만 `id(3)`에서 T를
추론하지 않는다. `mem.create(value)`처럼 별도로 정의된 compiler-known intrinsic
inference는 일반 generic inference가 아니다.
- generic struct/enum의 method는 enclosing type parameter를 사용할 수 있다. 그러나
method/function이 enclosing type parameter 외에 별도의 새 generic parameter list를
선언하는 generic-method 기능은 v0.1에 없다.
- 인스턴스화 시 타입 인자를 대입해 type-dependent operation을 재검사하고 코드를
생성한다. trait/bound와 overload resolution은 없다. 본문 연산이 해당 타입에서 invalid면
definition/body의 실제 연산 위치를 primary error로 표시하고 각 caller에 `instantiated
here` note를 붙인다. nested instance는 가능한 범위에서 instantiation chain을 표시한다.
- generic body의 이름은 항상 definition unit scope에서 해석한다. non-dependent name은
정의 시 그 symbol로 고정되며 caller의 같은 이름은 영향을 주지 않는다. private support
symbol도 definition unit의 것을 쓴다. `comptime if`의 선택되지 않는 branch는 parse만
하고 semantic name resolution/type checking/codegen을 하지 않는다.
- generic instance의 canonical key는 **canonical definition unit + canonical declaration
identity + canonical type argument list**다. type alias는 새 nominal identity가 아니므로
underlying/interned canonical type identity로 정규화한다. 따라서 `const Word = i32;` 뒤의
`id(Word, 1)``id(i32, 2)`는 같은 instance다.
- exported generic은 definition unit의 private symbol을 참조할 수 있다. 이는 컴파일러
수준의 처리이며 Ferro source visibility를 public으로 바꾸지 않는다 — 다른 Ferro source는
여전히 그 private 심볼을 직접 참조할 수 없다.
```fe
unit lib;
fn helper(x: i32) -> i32 { return x + 1; }
pub fn bump(comptime T: type, x: T) -> T {
comptime if T == i32 { return helper(x); }
return x;
}
```
다른 unit이 요청한 `lib.bump(i32)` instance는 내부적으로 `helper`를 호출할 수 있지만,
다른 Ferro source가 `lib.helper`를 직접 참조할 수는 없다.
- 재귀적 인스턴스화의 distinct-instance chain 제한은 32다. 이미 pending/known인 동일
canonical instance key를 다시 요청하는 recursion은 pending instance를 재사용하고 depth를
소비하지 않는다. 새로운 distinct instance가 연쇄적으로 생길 때만 depth가 증가하며
32를 초과하면 최초/현재 위치와 instance chain을 포함한 compile error를 낸다.
- comptime에서 type 값의 `==`/`!=`, `@is_int(T)`, `@is_ptr(T)`를 허용한다. canonical
interned type identity로 평가하며 런타임 type reflection은 없다.
---
## 10. 표준 라이브러리
표준 라이브러리 상세 명세는 별도 문서에서 다룬다. 표준 라이브러리는 reserved `std`
namespace 아래에 있으며 `import std.io;`처럼 명시적으로 가져온다. import 뒤의 local
binding은 마지막 segment라 `io.write`, `mem.replace` 형태로 사용한다.
아래는 언어 규칙(§4~§7)이 직접 참조하거나 언어 표면(빌트인, 예제)이 전제하는 최소
표면만 남긴 것이다. `std.list`, `std.map`, `std.io.File`의 전체 API, `std.sys`의 OS
접근 함수 등 나머지 모듈의 정확한 시그니처는 표준 라이브러리 명세가 정의한다.
- **`std.core`**: `panic`, `set_panic_handler`, `Error`(기본 에러 집합, §4.6), `assert`. `panic`/`set_panic_handler`는 §7.4 트랩 동작이 참조한다.
- **`std.mem`**: `create(value: T) -> !^T`(T는 값에서 추론), `destroy(p)`, `alloc_slice(T, n) -> !^[]T`, `replace(dst: &mut T, value: T) -> T`, `copy(dst: []mut u8, src: []u8)`, `set(dst: []mut u8, v: u8)`, `Arena{ init, alloc, reset, drop }`. 초기화되지 않은 힙을 안전 코드에 반환하는 `create(T)` 형태는 없다. `replace`는 이전 값을 이동해 반환하고 새 값으로 자리를 초기화하며 부분 이동과 재귀 구조의 반복 drop에 사용한다(§4.5, §5 R3·R7·R11).
- **문자열/바이트**: `str``[]u8` alias다(§4.2). 내장 alias 메서드 `eq`, `find`, `starts_with`, `split_at`, `parse_int`, `trim`, `to_cstr`, `from_cstr``line.trim()`처럼 호출하며(§6.4 예제) `str` 이름의 import 유닛은 두지 않는다.
- **`std.fmt`**: sink를 소유하지 않는 순수 변환 함수 모음이며 `@print`/`@fprint`/`@sprint`(§6.3.1)가 의존한다. `fmt_int_i8/i16/i32/u8/u16/u32(buf: []mut u8, v) -> str`, `fmt_hex_*`, `fmt_char`, `fmt_bool`, `fmt_error`, `fmt_int_pad`를 제공한다. 반환 slice는 buf에서 파생된 R8(a) 결과다. `fmt_error``--strip-error-names`를 따른다.
- **`std.io`**:
```fe
pub enum Writer { Stdout, Stderr, File(u16), Null }
pub enum Reader { Stdin, File(u16) }
```
둘 다 정수 payload만 가진 Copy handle이며 참조나 raw context pointer를 저장하지 않는다(§5 R8 예제). `io.write(w: Writer, buf: []u8) -> !usize`, `io.read(r: Reader, buf: []mut u8) -> !usize`가 실제 I/O를 수행한다.
- **`std.sys`**: `exit`, `on_exit(f: fn() -> void) -> !void`. `on_exit`은 §7.4 트랩 동작이 참조하는 allocation 없는 고정 크기 callback registry이며 가득 차면 오류를 반환한다.
---
## 11. 의도적으로 제외한 기능
**등급 정의**
- `영구` — §1 철학과 정면 충돌. v2.0에서도 넣지 않는다.
- `구조적 불가` — 넣으면 R4를 풀어야 하고 전역 분석이 생긴다(§1 철학 2). 이 언어의 정의상 불가.
- `v0.2` — 넣을 예정. 순서 문제일 뿐 원칙 위반 아님.
- `편의` — 원칙 위반 없음, 구현도 쉬움. 여유 생기면 아무 때나.
| 기능 | 등급 | 제외 이유 | 대체 수단 |
|---|---|---|---|
| 트레잇/인터페이스 (`dyn`) | **v0.2 (1순위)** | 부트스트랩에 불필요, 타입 시스템 전반에 영향 | Copy handle enum (`io.Writer`, §10) |
| 인터럽트 핸들러와 공유 상태 (`interrupt fn`, `shared`, `atomic`, `critical`) | v0.2 | 벡터 설치·복원과 배리어가 백엔드 지원을 요구하고, 타깃마다 다르다 | polling |
| far 포인터와 세그먼트 주소 지정 | **영구** | x86 리얼모드에만 있는 개념이고 평평한 주소 공간에는 대응물이 없다 (§2) | 없음. 리얼모드 타깃이 생기면 그때 다시 본다 |
| 클로저 | v0.2 | 캡처 = 참조 저장 = R4 위반 소지 | 콜백에 `ctx: *void` 전달 |
| 연산자 오버로딩 | v0.2 (인터페이스 이후) | 숨은 비용. 넣더라도 특정 인터페이스 구현으로만 제한 | 메서드 |
| 튜플 / 다중 반환 | 편의 | 이름 없는 필드는 가독성 손해 | struct |
| 레이블 있는 break | 편의 | — | 플래그 변수 |
| 슬라이스 패턴 매칭 | 편의 | — | 인덱스 비교 |
| `inline fn` | 편의 | — | 인라인 여부는 백엔드가 정한다 |
| `must` 키워드 | 편의 | 실패를 트랩으로 바꾸는 문법 설탕일 뿐 핵심 의미론이 아님 | `expr catch @trap()` |
| 블록 표현식 | 편의 | 값을 만드는 블록이 없으면 `catch`가 짧은 형태로 충분하고, 문법 표면이 작아진다 (§4.6) | `catch <식>`, `return`으로 탈출 |
| 라이프타임 표기 (`'a`) | **구조적 불가** | 전역 분석 필요, R4를 풀어야 함 | R4 (2급 참조), R8 파생 반환, 인덱스 핸들 |
| 선점형 스레드 | 구조적 불가 | DOS 기본 실행 모델에 없고 함수 단위 소유권 모델을 넘어서는 동기화가 필요 | — |
| 매크로 / 전처리기 | **영구** | 도구 지원과 컴파일 속도 파괴 | `const`, `comptime if`, 제네릭, `@print` |
| 예외 | 영구 | 언와인딩 기반 시설 없음, 숨은 비용 | 에러 유니온 |
| GC | 영구 | 결정적 비용 원칙 위반 | 소유권 + RAII + 아레나 |
| 암묵 형변환 | 영구 | 버그 원인 1위 | `as` |
| 상속 | 영구 | 숨은 vtable, 취약한 기반 클래스 | 합성 |
| 최상위 `comptime if` | v0.2 | 타깃이 하나이고 comptime 조건은 타입 술어뿐이라(§7.5) 유닛 바깥에는 물어볼 것이 없다 | 함수 안의 `comptime if`, 또는 유닛을 나눈다 |
### 11.1 인터페이스 설계 스케치 (v0.2 예정)
지금 구현하지 않되, 나중에 `io.Writer` Copy handle enum을 무리 없이 대체할 수 있도록 방향만 고정해 둔다.
```fe
pub interface Writer {
fn write(self: &mut Self, buf: []u8) -> !usize;
}
impl Writer for File { ... }
fn dump(w: &mut dyn Writer, data: []u8) -> !void { ... }
dump(&mut file, buf); // &mut File → &mut dyn Writer 자동 변환
```
- `dyn I`의 표현은 `(ctx, vtable)` 팻 포인터. vtable은 `(인터페이스, 구현 타입)` 쌍마다 `static const` 하나.
- **동적 디스패치 전용.** 제네릭 타입 제약(trait bound)으로는 쓸 수 없다 — 그걸 허용하면 전역 분석이 생긴다.
- `&dyn I`는 참조이므로 R4가 적용된다(필드 저장 불가). 필드에 담으려면 `^dyn I`(힙 박싱).
- `^dyn I`의 drop은 vtable 경유. 이 때문에 `?^dyn I`, drop 전개, 제네릭 인자로서의 `dyn` 등 타입 시스템 여러 곳에 케이스가 추가되므로 독립적으로 다룬다.
- 도입 시 `io.Writer`/`io.Reader` handle enum을 `dyn` 기반 API로 교체한다. v0.1 safe API에는 이미 대여 대상을 숨긴 `*void`가 없으므로 이 전환은 기능 확장이지 안전성 수정이 아니다.
위 표에 없는 항목(링크타임 최적화, 디버그 정보 포맷, 언어 서버 등)은 도구 영역이며 v0.2 이후 별도 검토.
+101
View File
@@ -0,0 +1,101 @@
# TODO
```
uv run python tests/run.py 245/245 컴파일러가 프로그램에 대해 뭐라고 하는가
uv run python tests/exec.py 38/38 컴파일된 프로그램이 실제로 무엇을 하는가
```
```
.fe → fec → i386 asm → wasm → wlink → .exe → Windows 11
```
---
## 셀프호스팅 전에 하기로 했던 것 — 전부 끝남
| | |
|---|---|
| `@print` / `@fprint` 전개 | SPEC §6.3.1 대로 컴파일 단계에서 편다. 진단 한 줄이 한 줄이다 |
| 파일과 명령줄 | 열기·읽기·쓰기·닫기, `argv`. 프로그램이 소스에 박힌 데이터 밖으로 나왔다 |
| `match` 페이로드와 `if let` | 태그드 유니온을 안전하게 해체한다 |
| 조용히 잘리던 상한 | 자란다. 넘쳐도 틀린 코드가 아니라 오류다 |
| `mem.Arena` | R11 이 말하는 아레나 + 핸들이 실제로 쓸 수 있다 |
| 레지스터 할당 | 블록 단위 선형 스캔. calc 4437 → 3796 줄 (-14%) |
| **Ferro 렉서를 Ferro 로** | 강제 함수. 돌아간다 |
| `std.map` | 키를 맵이 소유한 버퍼에 복사하고 슬롯은 위치만 든다 |
| **Ferro 파서를 Ferro 로** | 노드 배열 하나 + 인덱스. `1 + 2 * 3``(+ 1 (* 2 3))` 로 묶인다 |
| 필드 단위 대여 | `p.a``p.b` 는 다른 place 다. `std.map``keep` 이 다시 함수 하나가 됐다 |
| 외부 스펙 감사 대응 | 니치 옵셔널·store 폭·배치 세 버그, 스펙 빈칸, 죽은 C 백엔드, stdlib 다섯, 측정 |
파서가 알려준 것: **자기 참조 자료구조는 인덱스로 짓는다.** 노드는 `^Node`
들 수 없고(자식이 여럿이며 한 번씩 소유하지 않는다) `&Node` 도 들 수 없다(R4).
소유자 하나와 인덱스 여럿이 남는 유일한 모양이고, 그것으로 충분했다.
---
## 셀프호스팅으로 가는 길
| # | 일 | 규모 | 비고 |
|---|---|---|---|
| 1 | 리졸버를 Ferro 로 | 중 | 도구는 다 있다: `intern`, `map`, `Node.bind`, `Map.clear` |
| 2 | 타입 검사를 Ferro 로 | 대 | 리졸버 다음 |
| 3 | `io.read` 로 줄 단위 읽기 | 소 | 지금은 버퍼 하나로 통째로 읽는다 |
| 4 | 여러 반환값 또는 out 파라미터 | 중 | `&mut` 재대여로 되지만 장황하다 |
| 5 | `fec` 을 Ferro 로 | 대 | 여기까지 오면 언어가 자기 무게를 견딘다 |
## 언어에 남은 구멍
| | |
|---|---|
| `[value; count]` 배열 반복 리터럴 | 없다. 큰 버퍼는 `undefined` 로 선언한다 |
| 정수 폭 섞임 | `1 + 함수호출()` 같은 데서 뻑뻑하다. 리터럴이 늘 맞춰주지 않는다 |
| `@sprint` | 전개하지 않는다. `@print`/`@fprint` 만 |
| `interrupt` `shared` `atomic` `critical` | 파싱만 되고 의미 없음. SPEC §11 에서 v0.2 |
| lowering 미구현 진단 | `internal: cannot lower X`. 사용자 오류처럼 보이지 않는다 |
| 배열·enum 페이로드의 자동 해제 | `release_at` 은 구조체 필드까지만 내려간다. `?^T` 도 아직이라 `mem.replace` 로 직접 꺼내야 한다 |
| 컨테이너 두 원소의 동시 `&mut` | 인덱스는 갈라지지 않는다. `swap` 같은 것은 stdlib 안에서 해결한다 |
| `--strip-error-names` | 받아들이지만 아무것도 하지 않는다 (SPEC §4.6) |
| `fmt.fmt_error` | 없다. SPEC §4.6 이 약속만 하고 있다 |
---
## 정해진 것 — 언어
`SPEC.md` 가 유일한 출처다. 여기는 **어디를 보는지만** 적는다. 문장을 옮겨
적으면 한쪽만 고쳐져서 갈라진다.
| | |
|---|---|
| 타깃, `usize`/`isize` | §2 |
| 정수 리터럴의 타입과 범위 | §3 |
| 호출 인자·반환 위치의 대여 약화 | §4.2 |
| 해제는 소유자를 따라 내려간다 | §5 R1 |
| 대여는 place 단위 | §5 R6 |
| 배타 대여를 호출에 넘기는 것은 재대여 | §4.2, §5 R6 |
| 자기 `drop` 안의 부분 이동 | §5 R7 |
| `--no-checks` 의 오버플로는 랩어라운드 | §7.4 |
| `match`·`defer`·순회·`undefined` 등 작은 규칙 일곱 | §7.7 |
| 제네릭은 모노모피제이션 | §9 |
## 정해진 것 — 구현
`SPEC.md` 에 없는 것들. **여기가 출처다.**
| | | 사는 곳 |
|---|---|---|
| 덩어리 전달 | 전부 주소로. ISA 마다 다른 구조체 전달 ABI 를 피해감 | `lower.c` |
| 슬라이스 배치 | 포인터 다음 길이 | `lowerpri.h` 한 군데에만 |
| store 폭 | 값이 아니라 **자리**가 정한다 | `lowerstm.c` `store_into` |
| 트랩 | `fe_trap(reason, FE_FILE_n, line)`. 파일은 검사가 쓰인 유닛 | `x86.c`, `rt/start.asm` |
| 오류 코드 | 빌드 전체에서 모아 철자 순으로 1부터 | `lowerstm.c` |
| 레지스터 | ebx·esi·edi 를 블록 안에 머무는 임시값에. eax/ecx/edx 는 스크래치 | `x86.c` |
| 제네릭 인스턴스 | 짓는 중에는 배치하지 않는다. 멤버가 안 정해진 집합 타입은 굳지 않고 물러난다 | `types.c` `layout_type` |
| unsafe 예산 | `std.mem`/`std.sys` 밖은 0. 늘어나면 빌드가 실패한다 | `tests/run.py` |
---
## 파일 크기 규칙
**2,000 줄을 넘기지 않는다. 웬만하면 1,000 줄.** 넘으면 나눈다 -- `check.c`
3,937 줄이었고 `lower.c` 는 1,913 줄이었다. 지금 가장 큰 것은 `checkcal.c`
1,044 줄이다.
+77
View File
@@ -0,0 +1,77 @@
# Frontend gap audit
- 날짜: 2026-08-17
- 기준 커밋: `6dc298d828872409fdf6b7d2e85830f18a118d9f`
- 범위: parser, checker, 전역 lowering의 경계
- **해결: 11 건 전부와 `0b`/`0o` 리터럴까지. 모두 구현 쪽이었다.**
fixture 는 아래 표에 적었다. 본문은 조사 시점 그대로다.
## 재현된 문제
아래 최소 입력은 발견 시점의 `fec --check`를 모두 통과했다.
| ID | 문제 | 필요한 fixture |
|---|---|---|
| FRONT-01 | runtime 호출을 `const` 초기값으로 허용하고 lowering에서 초기값을 방출하지 않음 | `types/badcini.fe` |
| FRONT-02 | runtime 호출을 `static` 초기값으로 허용하고 저장소를 0으로 초기화 | `types/badsini.fe` |
| FRONT-03 | bool 비교 체이닝 허용: `true == false == true` | `types/badchain.fe` |
| FRONT-04 | 괄호 없는 단항식 뒤 cast 허용: `-x as u32` | `types/badunas.fe` |
| FRONT-05 | `unsafe` 밖에서 `asm` 허용 | `types/badasm.fe` |
| FRONT-06 | ABI 문자열 없는 `extern fn f();` 허용 | `types/badexns.fe` |
| FRONT-07 | `extern "c"` 이외 ABI 문자열 허용 | `types/badexab.fe` |
| FRONT-08 | extern 함수 본문 허용 | `types/badexbd.fe` |
| FRONT-09 | `extern`이 아닌 본문 없는 `fn f();`를 외부 심볼로 처리 | `types/badfnsm.fe` |
| FRONT-10 | 빈 enum 선언 허용 | `parse/bademen.fe` |
| FRONT-11 | 빈 error 선언 허용 | `parse/bademer.fe` |
중복 struct field와 중복 enum variant 선언도 통과했지만, 중복 선언 규칙을 SPEC에서 먼저
확정해야 하므로 위 목록에는 넣지 않았다.
## 해결
SPEC 은 FRONT-03·04(§6.2), 05(§5 R9), 06~09(§6.1·§7.3), 10·11(§6.1)을 이미
옳게 적고 있었다. 구현만 따라가지 않았다. FRONT-01·02 는 SPEC 에도 규칙이
없어서 §7.1 에 문장을 넣었다 -- 전역 초기값은 컴파일 시점에 알 수 있어야 한다.
| ID | fixture |
|---|---|
| FRONT-01 | `types/badcini.fe` |
| FRONT-02 | `types/badsini.fe` |
| FRONT-03 | `types/badchain.fe` |
| FRONT-04 | `types/badunas.fe` |
| FRONT-05 | `types/badasm.fe` |
| FRONT-06 | `types/badexns.fe` |
| FRONT-07 | `types/badexab.fe` |
| FRONT-08 | `types/badexbd.fe` |
| FRONT-09 | `types/badfnsm.fe` |
| FRONT-10 | `parse/bademen.fe` |
| FRONT-11 | `parse/bademer.fe` |
| 허용되는 짝 | `types/okglobin.fe` |
| `0b`/`0o` | `exec/radix.fe` |
`-x as T` 와 비교 체이닝을 구별하려면 괄호가 트리에 남아야 해서 노드에
`FE_NODE_PAREN` 을 두었다. 파싱 뒤에는 `-x as T``-(x as T)` 가 같은
트리다.
남은 것: `parse/` fixture 가 트리 내용을 비교하지 않는다는 지적은 그대로
유효하다. 우선순위는 지금 `exec/bitnot.fe` 처럼 실행 결과로 구별한다.
## 이미 알려진 실행 문제
`0b``0o` 리터럴은 lexer가 받지만 값 계산이 진법을 반영하지 않는다. 실행 결과를
고정하는 `exec/radix.fe`가 필요하다.
## 테스트 기반의 빈틈
`parse/` fixture는 `--dump-ast`의 성공 여부만 검사하고 트리 내용은 비교하지 않는다.
따라서 연산자 우선순위나 postfix 결합 방향은 parse fixture만으로 고정되지 않는다.
이런 항목은 실행 결과로 구별하거나 선택적인 AST 기대값 검사를 추가해야 한다.
## 검증
- `uv run python tests/run.py`: `245/245` 통과
- 각 문제를 독립적인 최소 입력으로 만들어 `--check` 결과를 확인함
- 조사용 임시 입력은 제거함
이 문서는 발견 시점의 상태를 기록한다. 작성 중인 미커밋 수정으로 일부 항목의 상태가
바뀔 수 있으므로 해결 여부는 fixture와 두 테스트 suite로 확인한다.
+44
View File
@@ -0,0 +1,44 @@
# SPECfec parser audit
- 날짜: 2026-08-17
- 기준 커밋: `52aaff62e490e37a0995aaaf7cbda47cf98e54a7`
- 범위: `SPEC.md` §6과 `fec/src/lexer.c`, `fec/src/parser.c`
- **해결: 일곱 전부. 구현 셋(PARSE-04a·05·06), SPEC 다섯(01·02·03·04b·07).**
판정과 근거는 아래 표에 덧붙였다. 본문은 조사 시점 그대로다.
## 현재 문제
| ID | SPEC | 현재 구현 | 재현 결과 |
|---|---|---|---|
| PARSE-01 | 최상위 `comptime if` 선언 허용 | `comptime if`는 문장에서만 처리 | 최상위 사용을 `expected declaration`으로 거부 |
| PARSE-02 | 타입 이름은 `ident ('.' ident)*` | 타입에서 점 하나만 처리 | `alpha.beta.Gamma`를 파싱하지 못함 |
| PARSE-03 | `catch` EBNF가 binding 없는 block과 binding 뒤 expression도 허용 | 짧은 `catch expr``catch \|e\| block`만 처리 | 구현은 §4.6 설명과 맞고 §6.1 EBNF가 지나치게 넓음 |
| PARSE-04 | `\|``^`는 같은 우선순위, 단항 비트 NOT은 `~` | 각각 우선순위 5와 6, `~` 토큰 없음, 단항 `^` 허용 | `~x`를 거부하고 `a \| b ^ c``a \| (b ^ c)`로 파싱 |
| PARSE-05 | 전역 `static``var`의 타입 필수 | 타입 표기를 선택적으로 처리하고 초기값에서 추론 | `static A = 1;`, `var B = 2;` 모두 검사 통과 |
| PARSE-06 | error code는 정수 literal | 일반 expression을 파싱하며 literal이 아니면 code 검증을 건너뜀 | `error E { Bad = 1 + 2, }`가 검사 통과 |
| PARSE-07 | struct field와 enum vfield의 쉼표 필수 | 닫는 `}` 바로 앞에서는 쉼표 생략 허용 | `struct S { x: i32 }`가 검사 통과 |
## 해결
| ID | 어느 쪽이 틀렸나 | 무엇을 했나 |
|---|---|---|
| PARSE-01 | SPEC | comptime 조건은 타입 술어뿐이라(§7.5) 유닛 바깥에는 물어볼 것이 없다. `comptime_decl``decl` 에서 빼고 §11 v0.2 로 |
| PARSE-02 | SPEC | `import` 는 unit path 의 마지막 segment 를 바인딩하므로 점 둘 이상인 타입 이름은 만들어질 수 없다. 문법을 `type_name := [ident '.'] ident` 로 |
| PARSE-03 | SPEC | §4.6 과 §11(블록 표현식 배제)이 실제 규칙이고 EBNF 가 넓었다. 두 형태로 나눠 적었다 |
| PARSE-04a | 구현 | `~` 를 넣었다. 렉서·파서·검사·lowering(`xor` with all ones) |
| PARSE-04b | SPEC | `\|``^` 를 한 단계로 두면 `a \| b ^ c``(a\|b)^c` 가 되어 C 에서 온 사람을 속인다. 구현(C 순서)이 옳아서 표를 쪼갰다 |
| PARSE-05 | 구현 | 전역 `static`/`var` 는 타입 필수. `const` 는 그대로 추론 |
| PARSE-06 | 구현 | error code 는 정수 리터럴 하나만 받는다 |
| PARSE-07 | SPEC | 마지막 쉼표 생략은 흔하고 `enum` 은 이미 허용하고 있었다. 명세에 적었다 |
fixture: `parse/badgtype.fe`, `parse/badecode.fe`, `parse/okglobal.fe`,
`exec/bitnot.fe`. 그리고 `tests/run.py` 가 마커를 진단 스트림에만 맞춘다 --
`--dump-ast` 모드에서 AST 덤프가 먼저 나와 마커가 못 쓰이고 있었다.
## 검증
- `uv run python tests/run.py`: `240/240` 통과
- 위 항목의 최소 입력을 현재 `fec`에 직접 넣어 파싱 및 `--check` 결과를 확인함
- 기존 fixture에는 위 괴리를 직접 고정하는 사례가 없음
이 문서는 조사 시점의 구현 상태를 기록한다. 언어 규칙의 기준은 `SPEC.md`다.
+11
View File
@@ -0,0 +1,11 @@
id = "ferro"
name = "Ferro"
version = "0.0.1"
schema_version = 1
authors = ["sebastianrcnt"]
description = "Ferro syntax highlighting for Zed"
repository = "https://github.com/sebastianrcnt/doslang"
[grammars.ferro]
repository = "https://github.com/sebastianrcnt/doslang"
rev = "40316bb34f5acbaf34979cdf2dce296ae752d81d"
+6
View File
@@ -0,0 +1,6 @@
name = "Ferro"
grammar = "ferro"
path_suffixes = ["fe"]
line_comments = ["// "]
tab_size = 4
hard_tabs = false
@@ -0,0 +1,12 @@
; Ferro syntax highlighting for Zed.
(line_comment) @comment
(block_comment) @comment
(string_literal) @string
(char_literal) @string
(integer_literal) @number
(builtin) @function.builtin
(builtin_type) @type.builtin
(keyword) @keyword
(operator) @operator
(punctuation) @punctuation.delimiter
+20
View File
@@ -0,0 +1,20 @@
CC ?= cc
CFLAGS ?= -O2 -Wall -Wextra -std=c89
CPPFLAGS ?= -Isrc
SRC = src/arena.c src/diag.c src/lexer.c src/ast.c src/parser.c src/types.c src/m7.c src/own.c src/check.c src/lower.c src/emit_c.c src/driver.c
OBJ = $(SRC:.c=.o)
.PHONY: all clean dos-build
all: fec
fec: $(OBJ)
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(OBJ)
src/%.o: src/%.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
dos-build:
@echo "Run build-dos.bat inside FreeDOS/Open Watcom."
clean:
$(RM) $(OBJ) fec
+1
View File
@@ -0,0 +1 @@
src\check.c(2795): Error! E1118: ***FATAL*** No such file or directory
+395
View File
@@ -0,0 +1,395 @@
; Ferro runtime: process entry and the trap handler.
;
; The entry point calls the program's `main` and hands its result to
; ExitProcess, so a Ferro program is an ordinary console executable.
; `fe_trap` prints where the program stopped and why, then exits 3.
.386
.model flat
extern _ExitProcess@4 : near
extern _GetStdHandle@4 : near
extern _WriteFile@20 : near
extern fe_main_ : near
_DATA segment dword public 'DATA'
reasons dd offset r_bounds, offset r_overflow, offset r_divide
dd offset r_unreach, offset r_explicit
r_bounds db 'index out of bounds',0
r_overflow db 'integer overflow',0
r_divide db 'divide by zero',0
r_unreach db 'reached unreachable code',0
r_explicit db 'trap',0
r_unknown db 'trap',0
prefix db 'ferro: ',0
at_word db ' at ',0
colon db ':',0
newline db 13,10,0
numbuf db 24 dup(0)
written dd 0
allocs dd 0
frees dd 0
_DATA ends
_TEXT segment dword public 'CODE'
; write_cstr(esi = pointer to a NUL-terminated string) -> void
write_cstr proc near
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov edi, esi
xor ecx, ecx
count_loop:
cmp byte ptr [edi], 0
je count_done
inc edi
inc ecx
jmp count_loop
count_done:
test ecx, ecx
je write_done
push -11 ; STD_ERROR_HANDLE
call _GetStdHandle@4
push 0 ; lpOverlapped
push offset written
push ecx
push esi
push eax
call _WriteFile@20
write_done:
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
write_cstr endp
; write_uint(eax = value) -> void
write_uint proc near
push ebp
mov ebp, esp
push ebx
mov edi, offset numbuf + 15
mov byte ptr [edi], 0
mov ebx, 10
digit_loop:
xor edx, edx
div ebx
add dl, '0'
dec edi
mov [edi], dl
test eax, eax
jnz digit_loop
mov esi, edi
call write_cstr
pop ebx
mov esp, ebp
pop ebp
ret
write_uint endp
; fe_trap(reason, file, line) -- cdecl, never returns
public fe_trap
fe_trap proc near
push ebp
mov ebp, esp
mov esi, offset prefix
call write_cstr
mov eax, [ebp+8] ; reason
cmp eax, 5
jb reason_ok
mov esi, offset r_unknown
jmp reason_write
reason_ok:
mov esi, [reasons + eax*4]
reason_write:
call write_cstr
mov esi, offset at_word
call write_cstr
mov esi, [ebp+12] ; file
call write_cstr
mov esi, offset colon
call write_cstr
mov eax, [ebp+16] ; line
call write_uint
mov esi, offset newline
call write_cstr
push 3
call _ExitProcess@4
fe_trap endp
; ---------------------------------------------------------------- primitives
; The standard library is written in Ferro; these are the few things it cannot
; say for itself. All cdecl.
extern _GetProcessHeap@0 : near
extern _HeapAlloc@12 : near
extern _HeapFree@12 : near
extern _CreateFileA@28 : near
extern _ReadFile@20 : near
extern _CloseHandle@4 : near
extern _GetCommandLineA@0 : near
; fe_rt_write(handle, ptr, len) -> bytes written
public fe_rt_write
fe_rt_write proc near
push ebp
mov ebp, esp
push ebx
mov eax, [ebp+8] ; 1 = stdout, 2 = stderr
cmp eax, 2
je pick_err
push -11
jmp pick_done
pick_err:
push -12
pick_done:
call _GetStdHandle@4
push 0
push offset written
push dword ptr [ebp+16]
push dword ptr [ebp+12]
push eax
call _WriteFile@20
mov eax, [written]
pop ebx
mov esp, ebp
pop ebp
ret
fe_rt_write endp
; fe_rt_alloc(n) -> pointer, or zero
public fe_rt_alloc
fe_rt_alloc proc near
push ebp
mov ebp, esp
call _GetProcessHeap@0
push dword ptr [ebp+8]
push 8 ; HEAP_ZERO_MEMORY
push eax
call _HeapAlloc@12
inc dword ptr [allocs]
mov esp, ebp
pop ebp
ret
fe_rt_alloc endp
; fe_rt_free(p)
public fe_rt_free
fe_rt_free proc near
push ebp
mov ebp, esp
mov eax, [ebp+8]
test eax, eax
je free_done
call _GetProcessHeap@0
push dword ptr [ebp+8]
push 0
push eax
call _HeapFree@12
inc dword ptr [frees]
free_done:
mov esp, ebp
pop ebp
ret
fe_rt_free endp
; fe_rt_allocs() / fe_rt_frees() -- what the allocator has been asked to do,
; so that a test can insist every allocation was released.
public fe_rt_allocs
fe_rt_allocs proc near
mov eax, [allocs]
ret
fe_rt_allocs endp
public fe_rt_frees
fe_rt_frees proc near
mov eax, [frees]
ret
fe_rt_frees endp
; fe_rt_write_int(handle, value, is_unsigned) -- decimal, with a sign when
; the value is negative and signed was asked for.
public fe_rt_write_int
fe_rt_write_int proc near
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov edi, offset numbuf + 15
mov byte ptr [edi], 0
mov eax, [ebp+12]
xor ebx, ebx ; ebx = 1 when a '-' is needed
cmp dword ptr [ebp+16], 0
jne int_digits
test eax, eax
jge int_digits
neg eax
mov ebx, 1
int_digits:
mov ecx, 10
int_loop:
xor edx, edx
div ecx
add dl, '0'
dec edi
mov [edi], dl
test eax, eax
jnz int_loop
test ebx, ebx
je int_write
dec edi
mov byte ptr [edi], '-'
int_write:
mov esi, offset numbuf + 15
sub esi, edi
push esi
push edi
push dword ptr [ebp+8]
call fe_rt_write
add esp, 12
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
fe_rt_write_int endp
; fe_rt_write_hex(handle, value)
public fe_rt_write_hex
fe_rt_write_hex proc near
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov edi, offset numbuf + 15
mov byte ptr [edi], 0
mov eax, [ebp+12]
hex_loop:
mov edx, eax
and edx, 15
cmp dl, 10
jb hex_digit
add dl, 'a' - 10 - '0'
hex_digit:
add dl, '0'
dec edi
mov [edi], dl
shr eax, 4
test eax, eax
jnz hex_loop
mov esi, offset numbuf + 15
sub esi, edi
push esi
push edi
push dword ptr [ebp+8]
call fe_rt_write
add esp, 12
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
fe_rt_write_hex endp
; fe_rt_open(path, write) -> handle, or -1
; `path` is a NUL-terminated byte string. Reading opens what is there; writing
; creates or truncates.
public fe_rt_open
fe_rt_open proc near
push ebp
mov ebp, esp
push 0 ; hTemplateFile
push 128 ; FILE_ATTRIBUTE_NORMAL
cmp dword ptr [ebp+12], 0
jne open_write
push 3 ; OPEN_EXISTING
push 0
push 1 ; FILE_SHARE_READ
push 80000000h ; GENERIC_READ
jmp open_call
open_write:
push 2 ; CREATE_ALWAYS
push 0
push 0
push 40000000h ; GENERIC_WRITE
open_call:
push dword ptr [ebp+8]
call _CreateFileA@28
mov esp, ebp
pop ebp
ret
fe_rt_open endp
; fe_rt_read(handle, buf, len) -> bytes read, or -1
public fe_rt_read
fe_rt_read proc near
push ebp
mov ebp, esp
push 0
push offset written
push dword ptr [ebp+16]
push dword ptr [ebp+12]
push dword ptr [ebp+8]
call _ReadFile@20
test eax, eax
jne read_ok
mov eax, -1
jmp read_done
read_ok:
mov eax, [written]
read_done:
mov esp, ebp
pop ebp
ret
fe_rt_read endp
; fe_rt_close(handle)
public fe_rt_close
fe_rt_close proc near
push ebp
mov ebp, esp
push dword ptr [ebp+8]
call _CloseHandle@4
mov esp, ebp
pop ebp
ret
fe_rt_close endp
; fe_rt_cmdline() -> pointer to the whole command line, NUL terminated.
; Splitting it is the standard library's job, not the runtime's.
public fe_rt_cmdline
fe_rt_cmdline proc near
call _GetCommandLineA@0
ret
fe_rt_cmdline endp
; fe_rt_exit(code) -- never returns
public fe_rt_exit
fe_rt_exit proc near
push ebp
mov ebp, esp
push dword ptr [ebp+8]
call _ExitProcess@4
fe_rt_exit endp
public fe_start_
fe_start_ proc near
call fe_main_
push eax
call _ExitProcess@4
fe_start_ endp
_TEXT ends
end fe_start_
+56
View File
@@ -0,0 +1,56 @@
#include "arena.h"
#include <stdlib.h>
#include <string.h>
struct FeArenaBlock {
FeArenaBlock *next;
size_t used;
size_t size;
unsigned char data[1];
};
void fe_arena_init(FeArena *a, size_t block_size)
{
a->blocks = 0;
a->block_size = block_size ? block_size : 16384;
}
void fe_arena_destroy(FeArena *a)
{
FeArenaBlock *b = a->blocks;
while (b) {
FeArenaBlock *n = b->next;
free(b);
b = n;
}
a->blocks = 0;
}
void *fe_arena_alloc(FeArena *a, size_t size)
{
FeArenaBlock *b;
size_t need;
if (size == 0) size = 1;
need = (size + 7u) & ~(size_t)7u;
b = a->blocks;
if (!b || b->used + need > b->size) {
size_t bs = a->block_size > need ? a->block_size : need;
b = (FeArenaBlock *)malloc(sizeof(FeArenaBlock) + bs - 1);
if (!b) return 0;
b->next = a->blocks;
b->used = 0;
b->size = bs;
a->blocks = b;
}
b->used += need;
return b->data + b->used - need;
}
char *fe_arena_strdup(FeArena *a, const char *s, size_t n)
{
char *p = (char *)fe_arena_alloc(a, n + 1);
if (!p) return 0;
if (n) memcpy(p, s, n);
p[n] = '\0';
return p;
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef FE_ARENA_H
#define FE_ARENA_H
#include <stddef.h>
typedef struct FeArenaBlock FeArenaBlock;
typedef struct FeArena {
FeArenaBlock *blocks;
size_t block_size;
} FeArena;
void fe_arena_init(FeArena *a, size_t block_size);
void fe_arena_destroy(FeArena *a);
void *fe_arena_alloc(FeArena *a, size_t size);
char *fe_arena_strdup(FeArena *a, const char *s, size_t n);
#endif
+39
View File
@@ -0,0 +1,39 @@
#include "ast.h"
#include <stdio.h>
void fe_ast_init(FeAst *a) { fe_arena_init(&a->arena, 32768); a->root=0; }
void fe_ast_destroy(FeAst *a) { fe_arena_destroy(&a->arena); a->root=0; }
FeNode *fe_node(FeAst *a, FeNodeKind k, FeLoc loc, const char *text, unsigned long len)
{
FeNode *n=(FeNode *)fe_arena_alloc(&a->arena,sizeof(FeNode));
if (!n) return 0;
n->kind=k; n->loc=loc; n->text=text?fe_arena_strdup(&a->arena,text,len):0;
n->a=n->b=n->c=n->children=n->next=0; n->cname=0; n->aux_text=0; n->aux_cname=0; n->sem_type=0; n->sem_context=0; n->sem_decl=0; n->flags=0; return n;
}
void fe_node_add(FeNode *parent, FeNode *child)
{
FeNode *p;
if (!child) return;
if (!parent->children) { parent->children=child; return; }
p=parent->children; while(p->next) p=p->next; p->next=child;
}
static void spaces(int n, FILE *out) { while(n-->0) fputc(' ',out); }
void fe_ast_dump(const FeNode *n, int indent, FILE *out)
{
const FeNode *c;
if (!n) return;
spaces(indent,out); fprintf(out,"(%s",fe_node_name(n->kind));
if (n->text) fprintf(out," %s",n->text);
fputc('\n',out);
if (n->a) fe_ast_dump(n->a,indent+2,out);
if (n->b) fe_ast_dump(n->b,indent+2,out);
if (n->c) fe_ast_dump(n->c,indent+2,out);
for(c=n->children;c;c=c->next) fe_ast_dump(c,indent+2,out);
spaces(indent,out); fputc(')',out); fputc('\n',out);
}
const char *fe_node_name(FeNodeKind k)
{
static const char *names[] = {"unit","import","fn","struct","enum","error","const","global","field","param","variant","block","let","var","expr-stmt","assign","if","while","for","match","arm","return","break","continue","defer","unsafe","asm","type","expr","binary","unary","call","index","member","literal","ident","struct-init","array-init","error"};
if ((unsigned)k >= sizeof(names)/sizeof(names[0])) return "node";
return names[k];
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef FE_AST_H
#define FE_AST_H
#include "arena.h"
#include "lexer.h"
#include <stdio.h>
typedef enum FeNodeKind {
FE_N_UNIT, FE_N_IMPORT, FE_N_FN, FE_N_STRUCT, FE_N_ENUM, FE_N_ERROR_DECL, FE_N_CONST,
FE_N_GLOBAL, FE_N_FIELD, FE_N_PARAM, FE_N_VARIANT, FE_N_BLOCK, FE_N_LET, FE_N_VAR,
FE_N_EXPR_STMT, FE_N_ASSIGN, FE_N_IF, FE_N_WHILE, FE_N_FOR, FE_N_MATCH, FE_N_ARM,
FE_N_RETURN, FE_N_BREAK, FE_N_CONTINUE, FE_N_DEFER, FE_N_UNSAFE, FE_N_ASM,
FE_N_TYPE, FE_N_EXPR, FE_N_BINARY, FE_N_UNARY, FE_N_CALL, FE_N_INDEX, FE_N_MEMBER,
FE_N_LITERAL, FE_N_IDENT, FE_N_STRUCT_INIT, FE_N_ARRAY_INIT, FE_N_ERROR_NODE
} FeNodeKind;
typedef struct FeNode FeNode;
typedef struct FeType FeType;
struct FeNode {
FeNodeKind kind;
FeLoc loc;
char *text;
FeNode *a;
FeNode *b;
FeNode *c;
FeNode *children;
FeNode *next;
/* Semantic information filled by checking; kept out of AST dumps. */
char *cname;
char *aux_text;
char *aux_cname;
FeType *sem_type;
/* Expected contextual wrapper, used by M7 for null/Some and E!T
success/failure construction without mutating the expression's type. */
FeType *sem_context;
FeNode *sem_decl;
unsigned flags;
};
/* Bits in FeNode.flags. 0x100 and above belong to own.h. */
#define FE_NODE_PACKED 0x1U
#define FE_NODE_STATIC 0x2U
#define FE_NODE_SHARED 0x4U
#define FE_NODE_PUB 0x8U
#define FE_NODE_COMPTIME 0x10U
#define FE_NODE_EXTERN 0x20U
/* An index expression that had `..` in it, so it makes a slice rather than
reaching an element. `x[a]` and `x[a..]` are otherwise the same shape. */
#define FE_NODE_SLICE 0x40U
/* This expression was written inside parentheses. `-x as T` is a mistake
and `-(x as T)` is not, and after parsing they are the same tree. */
#define FE_NODE_PAREN 0x80U
typedef struct FeAst {
FeArena arena;
FeNode *root;
} FeAst;
void fe_ast_init(FeAst *a);
void fe_ast_destroy(FeAst *a);
FeNode *fe_node(FeAst *a, FeNodeKind k, FeLoc loc, const char *text, unsigned long len);
void fe_node_add(FeNode *parent, FeNode *child);
void fe_ast_dump(const FeNode *n, int indent, FILE *out);
const char *fe_node_name(FeNodeKind k);
#endif
+790
View File
@@ -0,0 +1,790 @@
#include "checkpri.h"
FeType *unknown(FeCheck *c)
{
return fe_type_intern(&c->types, "<unknown>");
}
void err(FeCheck *c, FeLoc loc, const char *msg)
{
fe_diag_error(c->diags, loc, msg);
}
/* Only numbers and characters have an order (SPEC 6.2). */
int ordered_type(const FeType *t)
{
return t && (t->kind==FE_TYPE_INT || t->kind==FE_TYPE_CHAR);
}
int known(FeType *t)
{
return t && t->kind != FE_TYPE_UNKNOWN && t->kind != FE_TYPE_ERROR;
}
/* Is this a projection of `self` inside that type's own `drop`? */
int in_own_drop(FeCheckerState *s, FeNode *n)
{
FeNode *base;
if (!s->fn_node || !s->fn_node->text || strcmp(s->fn_node->text,"drop")!=0)
return 0;
base = n ? n->a : 0;
while (base && (base->kind==FE_N_MEMBER || base->kind==FE_N_INDEX))
base = base->a;
return base && base->kind==FE_N_IDENT && base->text &&
strcmp(base->text,"self")==0;
}
void mark_moved(FeCheckerState *s, FeNode *n, FeType *t)
{
FeSym *sym=0;
/* Inside a type's own `drop` the object is going away, so taking a field
out of it leaves nothing behind that anyone could read. That is the one
place R7 has nothing to protect. */
if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX) && in_own_drop(s,n))
return;
if (n && n->kind==FE_N_IDENT)
sym=find_symbol(s->scope,n->text ? n->text : "");
if (s->defer_depth != 0) {
/* A defer capture keeps the owner live until scope cleanup; its body
is not an immediate consuming use. */
fe_own_mark_consumed(s->c->diags,
sym ? &sym->moved : 0,
sym ? sym->decl : 0,
n,t,1);
return;
}
if (sym && t && !fe_own_is_copy_type(t)) {
if (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX) {
fe_diag_error(s->c->diags,n->loc,
"cannot move a non-Copy value out of a projection; use mem.replace");
return;
}
/* Reaching an identifier already ran FE_OWN_READ over it, and that
read reported the value as gone if it was. Running the move as well
reports the same sentence at the same column a second time, so stop
at the state the read left behind. */
if (sym->own.move != FE_OWN_AVAILABLE) return;
if (fe_own_access(s->c->diags,&sym->own,FE_OWN_MOVE,n->loc)) {
sym->moved=sym->own.move;
/* Keep the existing emitter contract: ownership-consuming AST
uses carry this flag, while FeOwnState is the diagnostic
authority. */
fe_own_mark_consumed(s->c->diags,&sym->moved,sym->decl,n,t,0);
}
return;
}
fe_own_mark_consumed(s->c->diags,
sym ? &sym->moved : 0,
sym ? sym->decl : 0,
n,t,s->defer_depth != 0);
}
int compatible(FeType *want, FeType *got, FeNode *value)
{
FeNode *item;
unsigned long count;
if (want && got && value && value->kind==FE_N_ARRAY_INIT &&
want->kind==FE_TYPE_ARRAY && got->kind==FE_TYPE_ARRAY) {
if (want->length != got->length) return 0;
count=0;
for (item=value->children; item; item=item->next) {
if (!compatible(want->elem,item->sem_type,item)) return 0;
if (item->kind==FE_N_LITERAL && item->text &&
fe_type_is_integer(want->elem) &&
fe_type_is_integer(item->sem_type) &&
item->text[0]!='\'' && item->text[0]!='"')
item->sem_type=want->elem;
++count;
}
if (count!=want->length) return 0;
value->sem_type=want;
return 1;
}
if (fe_type_equal(want, got)) return 1;
if (!known(want) || !known(got)) return 1;
return fe_type_is_integer(want) && fe_type_is_integer(got) && value &&
value->kind == FE_N_LITERAL && value->text &&
value->text[0] != '\'' && value->text[0] != '"';
}
/* Does passing `arg` to a parameter of type `param` lend it rather than give
it away? An exclusive borrow handed to a call comes back when the call
returns, so it is not a move. */
int call_reborrows(const FeType *param, const FeType *arg)
{
if (!param || !arg) return 0;
if (param->kind==FE_TYPE_REF && arg->kind==FE_TYPE_REF &&
param->ref_mut && arg->ref_mut) return 1;
if (param->kind==FE_TYPE_SLICE && arg->kind==FE_TYPE_SLICE &&
param->ref_mut && arg->ref_mut) return 1;
return 0;
}
/* Handing back less than you hold. R8 says a returned reference has to be
derived from a parameter or a static; given that, returning the shared form
of an exclusive one is safe -- the caller cannot do anything with `[]T` that
it could not do with `[]mut T`. Without this a method on `&Self` cannot hand
out a read-only view of what it owns. */
int return_weakens(const FeType *want, const FeType *got)
{
if (!want || !got) return 0;
if (want->kind==FE_TYPE_SLICE && got->kind==FE_TYPE_SLICE &&
!want->ref_mut && got->ref_mut)
return fe_type_equal(want->elem,got->elem);
if (want->kind==FE_TYPE_REF && got->kind==FE_TYPE_REF &&
!want->ref_mut && got->ref_mut)
return fe_type_equal(want->elem,got->elem);
return 0;
}
int explicit_castable(FeType *a, FeType *b)
{
if (!a || !b) return 0;
/* An enum without a payload is a number with names on it, so reading it
as one is a widening or narrowing and nothing more. The other direction
is not allowed: an arbitrary number is not a variant. */
if (a->kind == FE_TYPE_ENUM && !a->fields &&
(fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR)) return 1;
return (fe_type_is_integer(a) || a->kind == FE_TYPE_CHAR) &&
(fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR);
}
FeType *node_type(FeCheck *c, FeNode *n)
{
FeType *t;
if (!n) return unknown(c);
t = fe_type_from_ast(&c->types, n);
n->sem_type = t;
return t;
}
/* A link-visible name. A unit path has dots in it and a generic instance has
brackets and commas, none of which an assembler will accept, so everything
outside the portable identifier set becomes an underscore. */
char *unit_cname(FeCheck *c, const char *name)
{
char *u;
char *p;
unsigned long n;
unsigned long i;
u = c->ast->root && c->ast->root->text ? c->ast->root->text : "unit";
n = (unsigned long)strlen("fe_") + (unsigned long)strlen(u) +
(unsigned long)strlen(name ? name : "name") + 2UL;
p = (char *)fe_arena_alloc(&c->arena, n);
if (!p) return 0;
strcpy(p, "fe_");
strcat(p, u);
strcat(p, "_");
strcat(p, name ? name : "name");
for (i = 0; p[i]; ++i) {
char ch = p[i];
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') || ch == '_'))
p[i] = '_';
}
return p;
}
char *local_cname(FeCheck *c, const char *name)
{
char number[24];
char *p;
unsigned long n;
sprintf(number, "%u", c->local_serial++);
n = (unsigned long)strlen("fe_l_") + (unsigned long)strlen(name) +
(unsigned long)strlen(number) + 2UL;
p = (char *)fe_arena_alloc(&c->arena, n);
if (!p) return 0;
strcpy(p, "fe_l_");
strcat(p, name ? name : "local");
strcat(p, "_");
strcat(p, number);
return p;
}
FeScope *scope_new(FeCheckerState *s, FeScope *parent)
{
FeScope *scope;
scope = (FeScope *)fe_arena_alloc(&s->c->arena, sizeof(FeScope));
if (!scope) {
err(s->c, s->c->ast->root->loc, "out of memory creating scope");
return parent;
}
scope->parent = parent;
scope->items = 0;
scope->count = 0;
scope->capacity = 0;
return scope;
}
FeSym *find_current(FeScope *scope, const char *name)
{
unsigned i;
if (!scope) return 0;
for (i = scope->count; i > 0; --i)
if (strcmp(scope->items[i - 1].name, name) == 0)
return &scope->items[i - 1];
return 0;
}
FeSym *find_symbol(FeScope *scope, const char *name)
{
FeSym *sym;
while (scope) {
sym = find_current(scope, name);
if (sym) return sym;
scope = scope->parent;
}
return 0;
}
FeSym *add_symbol(FeCheckerState *s, FeScope *scope,
const char *name, FeType *type, FeNode *fn,
int mutable, int initialized, char *cname,
FeNode *decl)
{
FeSym *items;
unsigned capacity;
FeSym *sym;
if (!name) name = "<unnamed>";
if (find_current(scope, name)) {
err(s->c, decl ? decl->loc : s->c->ast->root->loc,
"duplicate declaration in scope");
return 0;
}
if (scope->count == scope->capacity) {
capacity = scope->capacity ? scope->capacity * 2U : 8U;
items = (FeSym *)fe_arena_alloc(&s->c->arena,
capacity * sizeof(FeSym));
if (!items) {
err(s->c, decl ? decl->loc : s->c->ast->root->loc,
"out of memory growing symbol scope");
return 0;
}
if (scope->items)
memcpy(items, scope->items, scope->count * sizeof(FeSym));
scope->items = items;
scope->capacity = capacity;
}
sym = &scope->items[scope->count++];
sym->name = name;
sym->cname = cname;
sym->type = type;
sym->fn = fn;
sym->mutable = mutable;
sym->initialized = initialized;
sym->moved = FE_OWN_AVAILABLE;
sym->decl = decl;
fe_own_state_init(&sym->own, initialized);
sym->borrow_root = 0;
sym->borrow_field = 0;
sym->borrow_mut = 0;
sym->borrow_defer = 0;
sym->owner = scope;
if (decl) {
decl->cname = cname;
decl->sem_type = type;
}
return sym;
}
/* Make `unit` the one being checked. Types intern against its name, cnames
are built from it, and diagnostics quote its source rather than whichever
file happened to be parsed last. */
void enter_unit(FeCheck *c, unsigned index)
{
FeUnit *u = &c->build->units[index];
c->unit = u;
c->ast = &u->ast;
c->types.unit_name = u->name[0] ? u->name : "unit";
fe_diags_source(c->diags, u->source, u->size);
}
static int enter_decl_hook(void *owner, const char *unit);
static void leave_decl_hook(void *owner, int back);
void fe_check_init(FeCheck *c, FeBuild *build, FeDiags *diags,
unsigned pointer_bits, int no_checks)
{
unsigned i;
fe_arena_init(&c->arena, 16384);
c->build = build;
c->unit = 0;
c->ast = build->count ? &build->units[0].ast : 0;
for (i = 0; i < FE_BUILD_UNIT_MAX; ++i) c->unit_scope[i] = 0;
c->diags = diags;
c->pointer_bits = pointer_bits;
c->local_serial = 0;
c->no_checks = no_checks;
fe_types_init(&c->types, &c->arena, pointer_bits);
c->types.unit_name = "unit";
c->types.instantiate = instantiate_type_node;
c->types.instantiate_owner = c;
c->types.enter_decl = enter_decl_hook;
c->types.leave_decl = leave_decl_hook;
c->instances = (FeInstance *)fe_arena_alloc(&c->arena,
(unsigned long)FE_GENERIC_INSTANCE_MAX * sizeof(FeInstance));
c->instance_count = 0;
c->instance_depth = 0;
}
void fe_check_destroy(FeCheck *c)
{
fe_arena_destroy(&c->arena);
}
unsigned unit_index(FeCheck *c, const FeUnit *u)
{
return (unsigned)(u - c->build->units);
}
/* An import introduces a local binding, so `binding.name` reaches into the
unit it names. A local of the same spelling wins -- shadowing a binding is
legal and means the local -- so this only answers when the base name is not
otherwise in scope. */
FeUnit *binding_unit(FeCheckerState *s, FeNode *base)
{
if (!base || base->kind!=FE_N_IDENT || !base->text) return 0;
if (!s->c->build || !s->c->unit) return 0;
if (find_symbol(s->scope,base->text)) return 0;
return fe_build_binding(s->c->build,s->c->unit,base->text);
}
/* SPEC 8.2: a declaration is visible outside its unit only with `pub`. */
int decl_is_public(const FeNode *decl)
{
return decl && (decl->flags & FE_NODE_PUB)!=0;
}
FeSym *unit_member(FeCheck *c, FeUnit *u, const char *name)
{
if (!u || !name) return 0;
return find_current(c->unit_scope[unit_index(c,u)],name);
}
/* A type another unit declares, or null if it declares no such type. Interning
is keyed on the declaring unit, so this cannot collide with a same-named
type here. */
FeType *unit_type(FeCheck *c, FeUnit *u, const char *name)
{
FeType *t;
if (!u || !name) return 0;
for (t=c->types.types;t;t=t->next)
if (t->unit && strcmp(t->name,name)==0 &&
strcmp(t->unit,u->name)==0 && t->kind!=FE_TYPE_UNKNOWN) return t;
return 0;
}
/* A field type is written in the unit that declared the type, so it has to be
resolved with that unit's imports in scope -- not with whichever unit
happens to be current when the walk reaches it. Returns the index to go back
to, or -1 when there is nowhere to go. */
int enter_declaring_unit(FeCheck *c, const char *unit_name)
{
unsigned i;
unsigned here;
if (!unit_name || !c->build || !c->unit) return -1;
here = unit_index(c,c->unit);
for (i=0;i<c->build->count;++i)
if (strcmp(c->build->units[i].name,unit_name)==0) {
if (i==here) return -1;
enter_unit(c,i);
return (int)here;
}
return -1;
}
/* The type layer calls these; it knows nothing about units beyond a name. */
static int enter_decl_hook(void *owner, const char *unit)
{
return enter_declaring_unit((FeCheck *)owner, unit);
}
static void leave_decl_hook(void *owner, int back)
{
enter_unit((FeCheck *)owner, (unsigned)back);
}
/* The AST declaration of a type another unit declares, for its visibility and
for its methods. */
FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name)
{
FeNode *n;
(void)c;
if (!u || !name) return 0;
for (n=u->ast.root ? u->ast.root->children : 0;n;n=n->next)
if ((n->kind==FE_N_STRUCT || n->kind==FE_N_ENUM ||
n->kind==FE_N_ERROR_DECL) && n->text &&
strcmp(n->text,name)==0) return n;
return 0;
}
/* Resolve a type written in another unit's source. Names in a signature mean
what they meant where the signature was written, not where it is called. */
FeType *node_type_in(FeCheck *c, const char *unit, FeNode *node)
{
const char *save=c->types.unit_name;
FeType *t;
if (unit) c->types.unit_name=unit;
t=node_type(c,node);
c->types.unit_name=save;
return t;
}
FeNode *find_method(FeCheck *c, FeType *owner, const char *name)
{
FeNode *decl;
FeNode *method;
if(!owner || !name) return 0;
if(owner->decl_node) {
for(method=owner->decl_node->children; method; method=method->next)
if(method->kind==FE_N_FN && method->text &&
strcmp(method->text,name)==0) return method;
return 0;
}
for(decl=c->ast->root ? c->ast->root->children : 0; decl; decl=decl->next)
if(decl->kind==FE_N_STRUCT && decl->text &&
strcmp(decl->text,owner->name)==0)
for(method=decl->children; method; method=method->next)
if(method->kind==FE_N_FN && method->text &&
strcmp(method->text,name)==0) return method;
return 0;
}
FeType *method_type(FeCheck *c, FeNode *node, FeType *owner)
{
if(node && node->kind==FE_N_TYPE && node->text &&
strcmp(node->text,"Self")==0) return owner;
if(node && node->kind==FE_N_TYPE && node->text &&
(strcmp(node->text,"&")==0 || strcmp(node->text,"&mut")==0) &&
node->a && node->a->text && strcmp(node->a->text,"Self")==0)
return fe_type_ref(&c->types,owner,strcmp(node->text,"&mut")==0);
/* The rest of a method's signature is written in the unit that declared
the type, so a name in it means what that unit means by it and not what
the caller happens to mean. */
{
int back=enter_declaring_unit(c,owner ? owner->unit : 0);
FeType *t=node_type(c,node);
if (back>=0) enter_unit(c,(unsigned)back);
return t;
}
}
unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap)
{
unsigned count=0;
unsigned i;
FeScope *p;
for (p=scope; p && count<cap; p=p->parent)
for (i=0; i<p->count && count<cap; ++i) {
slots[count].sym=&p->items[i];
slots[count].moved=p->items[i].moved;
slots[count].initialized=p->items[i].initialized;
slots[count].own_move=p->items[i].own.move;
slots[count].own_initialized=p->items[i].own.initialized;
++count;
}
return count;
}
void flow_restore(FeFlowSlot *slots, unsigned count)
{
unsigned i;
for (i=0; i<count; ++i) {
slots[i].sym->moved=slots[i].moved;
slots[i].sym->initialized=slots[i].initialized;
slots[i].sym->own.move=slots[i].own_move;
slots[i].sym->own.initialized=slots[i].own_initialized;
}
}
void flow_merge(FeFlowSlot *base, FeFlowSlot *left, FeFlowSlot *right,
unsigned count)
{
unsigned i;
for (i=0; i<count; ++i) {
base[i].sym->moved=fe_own_merge_move(left[i].moved,right[i].moved);
base[i].sym->initialized=left[i].initialized && right[i].initialized;
base[i].sym->own.move=fe_own_merge_move(left[i].own_move,right[i].own_move);
base[i].sym->own.initialized=left[i].own_initialized && right[i].own_initialized;
}
}
FeSym *own_root_symbol(FeCheckerState *s, FeNode *expr)
{
FeOwnPlace place;
if (!fe_own_place_from_expr(expr,&place)) return 0;
return find_symbol(s->scope,place.root->text ? place.root->text : "");
}
int own_is_global(FeCheckerState *s, FeSym *sym)
{
FeScope *p;
if (!s || !sym) return 0;
for (p=s->globals; p; p=p->parent) {
unsigned i;
for (i=0;i<p->count;++i) if (&p->items[i]==sym) return 1;
}
return 0;
}
/* Strip the `&`/`&mut` off an expression; the place underneath is what is
being reached. */
static FeNode *own_strip_ref(FeNode *e)
{
while (e && e->kind==FE_N_UNARY && e->text &&
(strcmp(e->text,"&")==0 || strcmp(e->text,"&mut")==0))
e=e->a;
return e;
}
/* The first field projected off the root of `expr`, and that root.
`self.bytes.^[i]` projects `bytes` off `self`. An index (`arr[i]`) and a
dereference (`p.^`) name no field, so they answer for the whole value --
which is what the checker did for everything before. */
const char *own_projected_field(FeNode *expr, FeNode **root_out)
{
FeNode *inner;
FeNode *outer=0;
if (root_out) *root_out=0;
inner=own_strip_ref(expr);
while (inner && (inner->kind==FE_N_MEMBER || inner->kind==FE_N_INDEX)) {
outer=inner;
inner=own_strip_ref(inner->a);
}
if (!inner || inner->kind!=FE_N_IDENT || !outer) return 0;
if (outer->kind!=FE_N_MEMBER) return 0;
/* A member node's own text is the token the postfix chain started at, not
the operator, so the spelling of the projection is what to look at:
`.?` carries nothing on the right and `.^` carries a caret. */
if (outer->text && (strcmp(outer->text,".?")==0 ||
strcmp(outer->text,".^")==0)) return 0;
if (!outer->b || !outer->b->text) return 0;
if (strcmp(outer->b->text,"^")==0) return 0;
if (root_out) *root_out=inner;
return outer->b->text;
}
void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable)
{
FeSym *root=own_root_symbol(s,expr);
const char *field=own_projected_field(expr,0);
if (!root) return;
if (mutable && root->type && root->type->kind==FE_TYPE_REF &&
!root->type->ref_mut) {
err(s->c,expr->loc,"cannot create mutable borrow from a shared reference");
return;
}
if (own_is_global(s,root) &&
!(root->decl && root->decl->kind==FE_N_GLOBAL &&
(root->decl->flags & 2U) && !mutable)) {
err(s->c,expr->loc,"cannot borrow a mutable global");
return;
}
fe_own_access_field(s->c->diags,&root->own,field,
mutable ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
expr->loc);
}
void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr)
{
FeSym *root;
if (!expr || expr->kind!=FE_N_UNARY || !expr->text) return;
if (strcmp(expr->text,"&")!=0 && strcmp(expr->text,"&mut")!=0) return;
root=own_root_symbol(s,expr->a);
if (!root) return;
{
const char *field=own_projected_field(expr->a,0);
if (strcmp(expr->text,"&mut")==0)
fe_own_release_exclusive_field(&root->own,field);
else fe_own_release_shared_field(&root->own,field);
}
}
/* Return-reference provenance is represented at call sites by retaining a
borrow of the unique reference-derived argument (or method receiver). */
FeSym *own_derived_call_root(FeCheckerState *s, FeNode *call)
{
FeNode *param;
FeNode *arg;
FeNode *source=0;
unsigned refs=0;
if (!call || call->kind!=FE_N_CALL || !call->sem_type ||
!fe_own_is_reference_like(call->sem_type)) return 0;
if (call->a && call->a->kind==FE_N_MEMBER && call->sem_decl) {
param=call->sem_decl->a ? call->sem_decl->a->children : 0;
if (param && param->text && strcmp(param->text,"self")==0)
return own_root_symbol(s,call->a->a);
}
if (!call->sem_decl) return 0;
param=call->sem_decl->a ? call->sem_decl->a->children : 0;
arg=call->children;
while (param && arg) {
FeType *t=node_type(s->c,param->a);
if (fe_own_is_reference_like(t)) { ++refs; source=arg; }
param=param->next;
arg=arg->next;
}
return refs==1 ? own_root_symbol(s,source) : 0;
}
void own_bind_derived_call(FeCheckerState *s, FeSym *binding,
FeNode *value)
{
FeSym *root;
if (!binding || !value || value->kind!=FE_N_CALL) return;
root=own_derived_call_root(s,value);
if (!root) return; /* Static provenance. */
if (root->borrow_root) root=root->borrow_root;
if (value->sem_type->kind==FE_TYPE_REF && value->sem_type->ref_mut)
fe_own_access(s->c->diags,&root->own,FE_OWN_BORROW_MUT,value->loc);
else
fe_own_access(s->c->diags,&root->own,FE_OWN_BORROW_SHARED,value->loc);
binding->borrow_root=root;
binding->borrow_field=0;
binding->borrow_mut=value->sem_type->kind==FE_TYPE_REF && value->sem_type->ref_mut;
}
int own_stmt_uses(FeNode *node, const char *name)
{
FeNode *x;
if (!node || !name) return 0;
if (node->kind==FE_N_IDENT && node->text && strcmp(node->text,name)==0)
return 1;
if (own_stmt_uses(node->a,name) || own_stmt_uses(node->b,name) ||
own_stmt_uses(node->c,name)) return 1;
for (x=node->children;x;x=x->next)
if (own_stmt_uses(x,name)) return 1;
return 0;
}
int own_defer_uses(FeNode *node, const char *name)
{
FeNode *x;
if (!node) return 0;
if (node->kind==FE_N_DEFER && own_stmt_uses(node->a,name)) return 1;
if (own_defer_uses(node->a,name) || own_defer_uses(node->b,name) ||
own_defer_uses(node->c,name)) return 1;
for (x=node->children;x;x=x->next)
if (own_defer_uses(x,name)) return 1;
return 0;
}
int own_contains_node(FeNode *node, FeNode *needle)
{
FeNode *x;
if (!node || !needle) return 0;
if (node==needle) return 1;
if (own_contains_node(node->a,needle) ||
own_contains_node(node->b,needle) ||
own_contains_node(node->c,needle)) return 1;
for (x=node->children;x;x=x->next)
if (own_contains_node(x,needle)) return 1;
return 0;
}
void own_release_after_stmt(FeCheckerState *s, FeScope *scope,
FeNode *stmt, int scope_end)
{
unsigned i;
FeScope *p;
const FeOwnLastUse *last;
for (p=scope;p;p=scope_end ? 0 : p->parent) for (i=0;i<p->count;++i) {
FeSym *ref=&p->items[i];
if (!ref->borrow_root) continue;
last=fe_own_last_use(&s->liveness,
ref->decl && ref->decl->text ? ref->decl->text : ref->name);
if (!scope_end && (ref->borrow_defer || !last || last->defer_extended ||
!own_contains_node(stmt,last->last_node))) continue;
if (ref->borrow_mut)
fe_own_release_exclusive_field(&ref->borrow_root->own,
ref->borrow_field);
else fe_own_release_shared_field(&ref->borrow_root->own,
ref->borrow_field);
ref->borrow_root=0;
ref->borrow_field=0;
}
}
/* Full borrow snapshots live in the AST arena, rather than on the 16-bit
compiler stack. The compact FeFlowSlot arrays retain the pre-M6 move and
initialization flow handling. */
FeOwnState *flow_own_new(FeCheckerState *s, unsigned count)
{
if (!s || !count) return 0;
return (FeOwnState *)fe_arena_alloc(&s->c->arena,
count*sizeof(FeOwnState));
}
void flow_own_capture(FeFlowSlot *slots, FeOwnState *states,
unsigned count)
{
unsigned i;
if (!states) return;
for (i=0;i<count;++i) states[i]=slots[i].sym->own;
}
void flow_own_restore(FeFlowSlot *slots, FeOwnState *states,
unsigned count)
{
unsigned i;
if (!states) return;
for (i=0;i<count;++i) slots[i].sym->own=states[i];
}
void flow_own_merge(FeFlowSlot *slots, FeOwnState *left,
FeOwnState *right, unsigned count)
{
unsigned i;
if (!left || !right) return;
for (i=0;i<count;++i)
slots[i].sym->own=fe_own_merge_state(left[i],right[i]);
}
FeFlowBorrow *flow_borrow_new(FeCheckerState *s, unsigned count)
{
if (!s || !count) return 0;
return (FeFlowBorrow *)fe_arena_alloc(&s->c->arena,
count*sizeof(FeFlowBorrow));
}
void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states,
unsigned count)
{
unsigned i;
if (!states) return;
for (i=0;i<count;++i) {
states[i].root=slots[i].sym->borrow_root;
states[i].field=slots[i].sym->borrow_field;
states[i].mutable=slots[i].sym->borrow_mut;
}
}
void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states,
unsigned count)
{
unsigned i;
if (!states) return;
for (i=0;i<count;++i) {
slots[i].sym->borrow_root=states[i].root;
slots[i].sym->borrow_field=states[i].field;
slots[i].sym->borrow_mut=states[i].mutable;
}
}
void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left,
FeFlowBorrow *right, unsigned count)
{
unsigned i;
if (!left || !right) return;
for (i=0;i<count;++i) {
slots[i].sym->borrow_root=left[i].root ? left[i].root : right[i].root;
slots[i].sym->borrow_field=left[i].root ? left[i].field : right[i].field;
slots[i].sym->borrow_mut=left[i].mutable || right[i].mutable;
}
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef FE_CHECK_H
#define FE_CHECK_H
#include "types.h"
#include "diag.h"
#include "resolve.h"
typedef struct FeScope FeScope;
/* One generic instance, identified by declaring unit, declaration and the
spelling of its type arguments (SPEC 9). The table both deduplicates
requests and bounds how long a chain of new ones can get. */
#define FE_GENERIC_KEY_MAX 320
#define FE_GENERIC_INSTANCE_MAX 4096
typedef struct FeInstance {
char key[FE_GENERIC_KEY_MAX];
/* What lowering needs to build this instance's code: the declaration, the
arguments bound while it was checked, the unit those names belong to,
and the name the linker will see. */
FeNode *decl;
FeTypeBind binds[FE_TYPE_PARAM_MAX];
unsigned bind_count;
const char *home;
const char *cname;
FeType *owner; /* set when the instance is a method */
} FeInstance;
/* The checker spans a whole build, not one file. Names cross unit boundaries,
so every unit's declarations have to exist before any unit's bodies are
looked at, and they all have to be interned in one type context or the same
spelling in two units would not be the same type. */
typedef struct FeCheck {
/* Scopes, symbols and types outlive whichever unit is current, so they
come from the checker's own arena rather than from an AST's. */
FeArena arena;
FeBuild *build;
FeAst *ast; /* the unit being checked now */
FeUnit *unit; /* its entry in the build */
FeScope *unit_scope[FE_BUILD_UNIT_MAX];
FeTypeCtx types;
FeDiags *diags;
unsigned pointer_bits;
unsigned local_serial;
int no_checks;
FeInstance *instances;
unsigned instance_count;
unsigned instance_depth;
} FeCheck;
void fe_check_init(FeCheck *c, FeBuild *build, FeDiags *diags,
unsigned pointer_bits, int no_checks);
void fe_check_destroy(FeCheck *c);
int fe_check_program(FeCheck *c);
#endif
+1050
View File
File diff suppressed because it is too large Load Diff
+806
View File
@@ -0,0 +1,806 @@
#include "checkpri.h"
FeNode *find_const_node(FeCheck *c, const char *name)
{
FeNode *n;
for (n=c->ast->root ? c->ast->root->children : 0; n; n=n->next)
if (n->kind==FE_N_CONST && n->text && name && strcmp(n->text,name)==0)
return n;
return 0;
}
const char *builtin_format(FeCheckerState *s, FeNode *fmt)
{
FeNode *decl;
FeSym *sym;
if (fmt && fmt->kind==FE_N_LITERAL && fmt->text && fmt->text[0]=='"')
return fmt->text;
if (fmt && fmt->kind==FE_N_IDENT) {
sym=find_symbol(s->scope,fmt->text);
decl=sym && sym->decl && sym->decl->kind==FE_N_CONST ?
sym->decl : find_const_node(s->c,fmt->text);
if (decl && decl->b && decl->b->kind==FE_N_LITERAL &&
decl->b->text && decl->b->text[0]=='"') {
if (!decl->a || format_is_slice_u8(fe_type_from_ast(&s->c->types,decl->a)))
return decl->b->text;
}
}
return 0;
}
int format_is_slice_u8(FeType *t)
{
return t && t->kind==FE_TYPE_SLICE && t->elem &&
t->elem->kind==FE_TYPE_INT && strcmp(t->elem->name,"u8")==0;
}
int format_is_writer_type(FeType *t)
{
return t && t->kind==FE_TYPE_STRUCT &&
(strcmp(t->name,"Writer")==0 || strcmp(t->name,"io.Writer")==0);
}
int format_arg_ok(FeType *t, int verb)
{
if (!t) return 0;
if (verb=='x') return fe_type_is_integer(t);
if (verb=='c') return t->kind==FE_TYPE_CHAR;
if (verb=='s') return format_is_slice_u8(t);
if (verb=='b') return t->kind==FE_TYPE_BOOL;
if (t->kind==FE_TYPE_INT || t->kind==FE_TYPE_BOOL ||
t->kind==FE_TYPE_CHAR) return 1;
return format_is_slice_u8(t) ||
(t->kind==FE_TYPE_ENUM && t->is_error);
}
void check_format_call(FeCheckerState *s, FeNode *n)
{
const char *fmt;
FeNode *fmt_node;
FeNode *arg;
FeNode *x;
FeType *t;
unsigned long i,j;
unsigned count=0;
unsigned argc=0;
unsigned offset=0;
int verb;
int bad=0;
int counted=0;
if (strcmp(n->text,"@fprint")==0) offset=1;
fmt_node=n->children;
if (offset) {
if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; }
t=check_expr(s,fmt_node);
if (!format_is_writer_type(t))
err(s->c,fmt_node->loc,"@fprint requires io.Writer");
fmt_node=fmt_node->next;
}
if (strcmp(n->text,"@sprint")==0) {
if (!fmt_node) { err(s->c,n->loc,"@sprint requires a buffer"); return; }
t=check_expr(s,fmt_node);
if (!format_is_slice_u8(t) || !t->ref_mut)
err(s->c,fmt_node->loc,"@sprint requires []mut u8 buffer");
fmt_node=fmt_node->next;
}
fmt=builtin_format(s,fmt_node);
if (!fmt) { err(s->c,n->loc,"format must be a comptime string"); return; }
n->aux_text=(char *)fmt;
arg=fmt_node ? fmt_node->next : 0;
for (x=arg;x;x=x->next) { check_expr(s,x); ++argc; }
i=1;
while (fmt[i] && fmt[i]!='"') {
if (fmt[i]=='\\') { if (fmt[i+1]) ++i; ++i; continue; }
if (fmt[i]=='{' && fmt[i+1]=='{') { i+=2; continue; }
if (fmt[i]=='}' && fmt[i+1]=='}') { i+=2; continue; }
if (fmt[i]=='{') {
j=i+1;
while (fmt[j] && fmt[j]!='}') ++j;
if (!fmt[j]) { err(s->c,n->loc,"unterminated format placeholder"); bad=1; break; }
if (j==i+1) verb=' '; else if (j==i+2) verb=(unsigned char)fmt[i+1]; else verb='?';
if (verb!=' ' && verb!='x' && verb!='c' && verb!='s' && verb!='b') {
err(s->c,n->loc,"unsupported format verb"); bad=1;
}
if (!arg) {
err(s->c,n->loc,"format argument count mismatch");
bad=1; counted=1;
}
else {
t=arg->sem_type;
if (verb==' ' && t && t->kind==FE_TYPE_ENUM && t->is_error) verb='s';
if (!format_arg_ok(t,verb)) { err(s->c,arg->loc,"no fmt writer for argument type"); bad=1; }
arg=arg->next;
}
++count; i=j+1; continue;
}
if (fmt[i]=='}') { err(s->c,n->loc,"unmatched '}' in format"); bad=1; }
++i;
}
/* Running out of arguments mid-string already said this. Saying it again
once the whole string has been walked adds nothing. */
if (count!=argc && !counted) { err(s->c,n->loc,"format argument count mismatch"); bad=1; }
(void)bad;
}
int is_format_builtin(const char *name)
{
return name && (strcmp(name,"@print")==0 || strcmp(name,"@fprint")==0 ||
strcmp(name,"@sprint")==0);
}
int lvalue_writable(FeCheckerState *s, FeNode *n)
{
FeSym *sym;
FeType *t;
if (!n) return 0;
if (n->kind == FE_N_IDENT) {
sym=find_symbol(s->scope,n->text ? n->text : "");
return sym ? sym->mutable : 0;
}
if (n->kind == FE_N_MEMBER) {
t=n->a ? n->a->sem_type : 0;
if (t && t->kind==FE_TYPE_REF && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) return t->ref_mut;
/* Through an owner, what may be written is decided by what is owned,
not by whether the binding may be pointed somewhere else. `let p:
^[]mut T` fixes p and leaves what it owns writable. */
if (t && t->kind==FE_TYPE_OWNED && n->b && n->b->text &&
strcmp(n->b->text,"^")==0)
return !t->elem || t->elem->kind!=FE_TYPE_SLICE || t->elem->ref_mut;
return lvalue_writable(s,n->a);
}
if (n->kind == FE_N_INDEX) {
/* An index into a slice asks the slice, not the binding. */
t=n->a ? n->a->sem_type : 0;
if (t && t->kind==FE_TYPE_SLICE) return t->ref_mut;
return lvalue_writable(s,n->a);
}
return 0;
}
int has_field(FeNode *list, const char *name)
{
FeNode *f;
for (f=list; f; f=f->next)
if (f->text && name && strcmp(f->text,name)==0) return 1;
return 0;
}
/* A field of a type declared elsewhere is reachable only with `pub`. Inside
the declaring unit every field is reachable, `pub` or not. */
int field_is_visible(FeCheckerState *s, const FeType *t,
const FeFieldType *field)
{
if (!t || !t->unit) return 1;
if (s->c->types.unit_name &&
strcmp(t->unit,s->c->types.unit_name)==0) return 1;
return field && field->ast_node &&
(field->ast_node->flags & FE_NODE_PUB)!=0;
}
/* The field list of a struct literal, once the type is known. Reached from
both `Type{...}` and `binding.Type{...}`. */
FeType *check_struct_fields(FeCheckerState *s, FeNode *n, FeType *t)
{
FeFieldType *field;
FeNode *f;
FeType *v;
unsigned i;
for(f=n->children;f;f=f->next) if(f->kind==FE_N_FIELD) {
if(has_field(f->next,f->text)) { err(s->c,f->loc,"duplicate struct field"); }
field=fe_type_field(t,f->text);
if(!field) { err(s->c,f->loc,"invalid struct field"); continue; }
if(!field_is_visible(s,t,field)) {
err(s->c,f->loc,"field is private to its unit");
continue;
}
v=check_expr(s,f->a);
mark_moved(s,f->a,v);
if(!compatible(field->type,v,f->a) && v->kind!=FE_TYPE_UNKNOWN) err(s->c,f->loc,"struct field type mismatch");
}
for(i=0;i<t->field_count;i++) if(!has_field(n->children,t->fields[i].name)) err(s->c,n->loc,"missing struct field");
n->sem_type=t; return t;
}
FeType *check_struct_init(FeCheckerState *s, FeNode *n)
{
FeType *t;
FeFieldType *field;
FeNode *f;
FeType *v;
FeType *et;
FeVariantType *variant;
if (n->a && n->a->kind == FE_N_MEMBER) {
FeUnit *home=binding_unit(s,n->a->a);
if (home) {
/* `binding.Type{...}` names a type in another unit. */
const char *want=n->a->b && n->a->b->text ? n->a->b->text : "";
FeNode *decl=unit_type_decl(s->c,home,want);
t=unit_type(s->c,home,want);
if (!t || !decl) { err(s->c,n->a->loc,"unknown name"); return unknown(s->c); }
if (!decl_is_public(decl)) {
err(s->c,n->a->loc,"type is private to its unit");
return unknown(s->c);
}
if (t->kind!=FE_TYPE_STRUCT) {
err(s->c,n->loc,"unknown struct type");
return unknown(s->c);
}
return check_struct_fields(s,n,t);
}
et=check_expr(s,n->a->a);
variant=et && et->kind==FE_TYPE_ENUM ?
fe_type_variant(et,n->a->b ? n->a->b->text : "") : 0;
if (!variant) { err(s->c,n->loc,"invalid enum variant"); return unknown(s->c); }
if (variant->field_count != 0) {
for (f=n->children; f; f=f->next) {
if (f->kind != FE_N_FIELD) continue;
field=0;
if (variant->fields) {
unsigned i;
for(i=0;i<variant->field_count;i++) if(strcmp(variant->fields[i].name,f->text)==0) field=&variant->fields[i];
}
if (!field) { err(s->c,f->loc,"invalid enum payload field"); continue; }
v=check_expr(s,f->a);
mark_moved(s,f->a,v);
if (!compatible(field->type,v,f->a) && v->kind!=FE_TYPE_UNKNOWN) err(s->c,f->loc,"enum payload type mismatch");
}
} else if (n->children) err(s->c,n->loc,"empty enum variant cannot have payload");
n->sem_type=et; return et;
}
if (n->a && n->a->kind==FE_N_CALL) {
/* `Name(args){...}` -- the same spelling a type annotation uses, so
the same resolver answers it. */
int ok=0;
t=type_from_expr(s,n->a,&ok);
if (!ok || !t || t->kind!=FE_TYPE_STRUCT) {
err(s->c,n->a->loc,"unknown struct type");
return unknown(s->c);
}
return check_struct_fields(s,n,t);
}
t=fe_type_intern(&s->c->types,n->text ? n->text : "<unknown>");
if (!t || t->kind!=FE_TYPE_STRUCT) { err(s->c,n->loc,"unknown struct type"); return unknown(s->c); }
return check_struct_fields(s,n,t);
}
FeType *check_array_init(FeCheckerState *s, FeNode *n)
{
FeNode *x; FeType *elem=0; FeType *v; unsigned long count=0;
for(x=n->children;x;x=x->next) { v=check_expr(s,x); mark_moved(s,x,v); if(!elem) elem=v; else if(!compatible(elem,v,x)&&v->kind!=FE_TYPE_UNKNOWN) err(s->c,x->loc,"array element type mismatch"); ++count; }
if(!elem) elem=unknown(s->c);
n->sem_type=fe_type_array(&s->c->types,count,elem); return n->sem_type;
}
int array_slice_lvalue(FeNode *n)
{
return n && (n->kind==FE_N_IDENT || n->kind==FE_N_MEMBER ||
n->kind==FE_N_INDEX);
}
FeType *check_index(FeCheckerState *s, FeNode *n)
{
FeType *base=check_expr(s,n->a); FeType *idx; FeType *elem;
if(!fe_type_is_indexable(base)) { err(s->c,n->loc,"indexing requires an array or slice"); return unknown(s->c); }
if(n->b) { idx=check_expr(s,n->b); if(known(idx)&&!fe_type_is_integer(idx)) err(s->c,n->loc,"index must be an integer"); }
if(n->c || !n->b) {
if (base->kind==FE_TYPE_ARRAY && !array_slice_lvalue(n->a))
err(s->c,n->loc,"array slicing requires a stable lvalue");
if(n->c) {
idx=check_expr(s,n->c);
if(known(idx)&&!fe_type_is_integer(idx))
err(s->c,n->loc,"slice bound must be an integer");
}
elem=base->elem;
n->sem_type=(base->kind==FE_TYPE_SLICE ? base->ref_mut :
lvalue_writable(s,n->a)) ?
fe_type_mut_slice(&s->c->types,elem) :
fe_type_slice(&s->c->types,elem);
return n->sem_type;
}
n->sem_type=base->elem; return n->sem_type;
}
FeType *check_identifier(FeCheckerState *s, FeNode *n)
{
FeSym *sym;
sym = find_symbol(s->scope, n->text ? n->text : "");
if (!sym) {
FeType *named=fe_type_intern(&s->c->types,n->text ? n->text : "");
if(named->kind==FE_TYPE_STRUCT || named->kind==FE_TYPE_ENUM) { n->sem_type=named; return named; }
if(named->kind!=FE_TYPE_UNKNOWN) {
err(s->c, n->loc, "a type is not a value here");
return unknown(s->c);
}
err(s->c, n->loc, "unknown name");
return unknown(s->c);
}
n->cname = sym->cname;
n->sem_type = sym->type;
if (!sym->fn) {
/* When this identifier is the base of a projection, the read reaches
one field and not the whole value. The chain above left word. */
const char *field = s->proj_base==n ? s->proj_field : 0;
fe_own_access_field(s->c->diags,&sym->own,field,FE_OWN_READ,n->loc);
sym->moved=sym->own.move;
}
return sym->type;
}
FeType *check_expr_core(FeCheckerState *s, FeNode *n)
{
FeCheck *c = s->c;
FeType *a;
FeType *b;
FeSym *sym;
FeNode *x;
FeNode *param;
FeNode *arg;
FeType *et;
FeFieldType *field;
FeVariantType *variant;
const char *op;
if (!n) return unknown(c);
if (n->kind == FE_N_IDENT)
return check_identifier(s, n);
if (n->kind == FE_N_LITERAL) {
if (!n->text) return unknown(c);
if (strcmp(n->text, "true") == 0 || strcmp(n->text, "false") == 0)
a = fe_type_intern(&c->types, "bool");
else if (n->text[0] == '\'')
a = fe_type_intern(&c->types, "char");
else if (n->text[0] == '"')
a = fe_type_intern(&c->types, "str");
else
a = fe_type_intern(&c->types, "i32");
n->sem_type = a;
return a;
}
if (n->kind == FE_N_STRUCT_INIT) return check_struct_init(s,n);
if (n->kind == FE_N_ARRAY_INIT) return check_array_init(s,n);
if (n->kind == FE_N_INDEX) return check_index(s,n);
if (n->kind == FE_N_MATCH) { check_match(s,n); n->sem_type=unknown(c); return n->sem_type; }
if (n->kind == FE_N_UNARY) {
a = check_expr(s, n->a);
op = n->text ? n->text : "";
if (strcmp(op, "not") == 0) {
if (known(a) && a->kind != FE_TYPE_BOOL)
err(c, n->loc, "'not' requires bool");
a = fe_type_intern(&c->types, "bool");
} else if (strcmp(op, "-") == 0) {
if (known(a) && !fe_type_is_integer(a))
err(c, n->loc, "unary '-' requires integer");
} else if (strcmp(op, "~") == 0) {
/* Flipping every bit only means something where the bits are the
value (SPEC 6.2). */
if (known(a) && !fe_type_is_integer(a))
err(c, n->loc, "unary '~' requires integer");
} else if (strcmp(op, "try") == 0) {
/* SPEC 6.4: try is only allowed inside a function returning an error
union. Checked on the expression rather than on the statement so
that it also covers `var x = try e;` and `x = try e;`, which the
statement-level check walked straight past. */
if (!s->ret || s->ret->kind != FE_TYPE_ERROR_UNION)
err(c,n->loc,"try requires an enclosing error result");
if (a && a->kind==FE_TYPE_ERROR_UNION)
a=a->error_value;
else {
err(c,n->loc,"try requires an error result");
a=unknown(c);
}
} else if (strcmp(op,"&")==0 || strcmp(op,"&mut")==0) {
if (strcmp(op,"&mut")==0 && a && a->kind==FE_TYPE_REF && !a->ref_mut)
err(c,n->loc,"cannot create mutable borrow from a shared reference");
own_borrow_expr(s,n->a,strcmp(op,"&mut")==0);
a=fe_type_ref(&c->types,a,strcmp(op,"&mut")==0);
}
n->sem_type = a;
return a;
}
if (n->kind == FE_N_TYPE && n->text && strcmp(n->text, "as") == 0) {
a = check_expr(s, n->a);
b = node_type(c, n->b);
if (b->kind == FE_TYPE_VOID)
err(c, n->loc, "cast target cannot be void");
else if (known(a) && known(b) && !explicit_castable(a,b))
err(c, n->loc, "'as' requires integer or char types");
n->sem_type = b;
return b;
}
if (n->kind == FE_N_BINARY) {
a = check_expr(s, n->a);
b = check_expr(s, n->b);
op = n->text ? n->text : "";
if (strcmp(op, "and") == 0 || strcmp(op, "or") == 0) {
if ((known(a) && a->kind != FE_TYPE_BOOL) ||
(known(b) && b->kind != FE_TYPE_BOOL))
err(c, n->loc, "logical operator requires bool operands");
a = fe_type_intern(&c->types, "bool");
} else if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 ||
strcmp(op, "<") == 0 || strcmp(op, "<=") == 0 ||
strcmp(op, ">") == 0 || strcmp(op, ">=") == 0) {
if (known(a) && known(b) && !fe_type_equal(a, b) &&
!compatible(a, b, n->b) && !compatible(b, a, n->a))
err(c, n->loc, "comparison operands have different types");
else if (strcmp(op,"==")!=0 && strcmp(op,"!=")!=0 &&
((known(a) && !ordered_type(a)) ||
(known(b) && !ordered_type(b))))
err(c, n->loc, "ordering requires integer or char operands");
a = fe_type_intern(&c->types, "bool");
} else {
if ((known(a) && !fe_type_is_integer(a)) ||
(known(b) && !fe_type_is_integer(b)) ||
(known(a) && known(b) && !fe_type_equal(a, b) &&
!compatible(a, b, n->b) && !compatible(b, a, n->a)))
err(c, n->loc,
"arithmetic operands must have the same integer type");
}
n->sem_type = a;
return a;
}
if (n->kind == FE_N_CALL) {
if (n->a && n->a->kind==FE_N_MEMBER && n->a->b && n->a->b->text &&
strcmp(n->a->b->text,"drop")==0) {
err(c,n->loc,"drop may only be invoked by scope cleanup");
return unknown(c);
}
if (n->a && n->a->kind==FE_N_MEMBER && n->a->a &&
n->a->a->kind==FE_N_IDENT && n->a->a->text &&
strcmp(n->a->a->text,"mem")==0 && n->a->b && n->a->b->text) {
FeNode *arg=n->children;
if (strcmp(n->a->b->text,"destroy")==0) {
a=arg ? check_expr(s,arg) : unknown(c);
if (!arg || arg->next || !a || a->kind!=FE_TYPE_OWNED)
err(c,n->loc,"mem.destroy requires exactly one owned pointer");
else
mark_moved(s,arg,a);
n->sem_type=fe_type_intern(&c->types,"void");
return n->sem_type;
}
if (strcmp(n->a->b->text,"create")==0) {
if (!arg || arg->next)
err(c,n->loc,"mem.create requires exactly one value");
a=arg ? check_expr(s,arg) : unknown(c);
if(arg) mark_moved(s,arg,a);
a=fe_type_owned(&c->types,a);
n->sem_type=fe_type_error_union(&c->types,a);
return n->sem_type;
}
if (strcmp(n->a->b->text,"alloc_slice")==0) {
FeNode *count=arg ? arg->next : 0;
FeType *item;
{
/* The element type may be an instance -- `Slot(V)` -- and
not just a name. */
int named=0;
item=arg ? type_from_expr(s,arg,&named) : unknown(c);
if(!arg || !named || !count || count->next) {
err(c,n->loc,"mem.alloc_slice requires a type and length");
item=unknown(c);
}
}
b=count ? check_expr(s,count) : unknown(c);
if(known(b) && !fe_type_is_integer(b))
err(c,count->loc,"slice length must be an integer");
/* Freshly allocated storage is owned outright, so it is
writable: there is nobody else to disturb. */
a=fe_type_owned(&c->types,fe_type_mut_slice(&c->types,item));
n->sem_type=fe_type_error_union(&c->types,a);
return n->sem_type;
}
if (strcmp(n->a->b->text,"replace")==0) {
FeNode *value=arg ? arg->next : 0;
if(!arg || !value || value->next)
err(c,n->loc,"mem.replace requires destination and value");
a=arg ? check_expr(s,arg) : unknown(c);
if(!a || a->kind!=FE_TYPE_REF || !a->ref_mut ||
!arg->a || !lvalue_writable(s,arg->a))
err(c,n->loc,"mem.replace destination must be a mutable place");
b=value ? check_expr(s,value) : unknown(c);
if(a && a->kind==FE_TYPE_REF && !compatible(a->elem,b,value))
err(c,value->loc,"mem.replace value type mismatch");
if(value) mark_moved(s,value,b);
/* The destination is lent for the length of the call, the same
as any other argument. Without this the borrow stays live to
the end of the function and the place can never be read. */
own_release_temporary_borrow(s,arg);
n->sem_type=a && a->kind==FE_TYPE_REF ? a->elem : unknown(c);
fe_type_require_replace(&c->types,n->sem_type);
return n->sem_type;
}
}
if (n->a && n->a->kind==FE_N_MEMBER && n->a->a &&
n->a->a->kind==FE_N_IDENT && n->a->a->text &&
strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text &&
strcmp(n->a->b->text,"null_writer")==0) {
FeNode *arg=n->children;
if (arg) err(c,n->loc,"io.null_writer takes no arguments");
n->sem_type=fe_type_intern(&c->types,"io.Writer");
return n->sem_type;
}
if (n->text && is_format_builtin(n->text)) {
check_format_call(s,n);
if (strcmp(n->text,"@print")==0)
n->sem_type=fe_type_intern(&c->types,"void");
else if (strcmp(n->text,"@sprint")==0)
n->sem_type=fe_type_intern(&c->types,"usize");
else
n->sem_type=fe_type_error_union(&c->types,fe_type_intern(&c->types,"void"));
return n->sem_type;
}
if (!n->a && n->text && (strcmp(n->text,"@size_of")==0 || strcmp(n->text,"@align_of")==0)) {
FeNode *type_arg=n->children;
FeType *target=type_arg && type_arg->kind==FE_N_IDENT ? fe_type_intern(&c->types,type_arg->text) : unknown(c);
if(!target || !known(target)) err(c,n->loc,"size/align requires a known type");
n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type;
}
if (!n->a && n->text && strcmp(n->text,"@ptr_cast")==0) {
/* `@ptr_cast(T, p)`: the first argument names the type the result
points at, the second is the address. R9 keeps it in `unsafe`. */
FeNode *type_arg=n->children;
FeNode *value=type_arg ? type_arg->next : 0;
FeType *target=type_arg && type_arg->kind==FE_N_IDENT ?
fe_type_intern(&c->types,type_arg->text) : unknown(c);
if (!type_arg || !value || value->next)
err(c,n->loc,"@ptr_cast requires a type and a pointer");
if (value) check_expr(s,value);
n->sem_type=fe_type_raw(&c->types,target);
return n->sem_type;
}
if (n->a && n->a->kind == FE_N_MEMBER) {
FeNode *method;
FeNode *self_param;
FeUnit *home=binding_unit(s,n->a->a);
if (home) {
const char *want=n->a->b && n->a->b->text ? n->a->b->text : "";
FeSym *fsym=unit_member(c,home,want);
if (!fsym) {
err(c,n->a->loc,"unknown name");
for (x=n->children;x;x=x->next) check_expr(s,x);
return unknown(c);
}
if (!decl_is_public(fsym->decl)) {
err(c,n->a->loc,"name is private to its unit");
for (x=n->children;x;x=x->next) check_expr(s,x);
return unknown(c);
}
if (decl_is_generic(fsym->fn))
return check_generic_call(s,n,fsym,home);
return check_call_args(s,n,fsym,home->name,0);
}
{
int names_type=0;
FeType *owner_type=type_from_expr(s,n->a->a,&names_type);
if (names_type && owner_type &&
owner_type->kind==FE_TYPE_STRUCT) {
FeNode *m=type_method(owner_type,
n->a->b ? n->a->b->text : "");
if (!m) { err(c,n->a->loc,"unknown method"); return unknown(c); }
if (!method_is_static(m)) {
err(c,n->loc,"method requires a receiver");
return unknown(c);
}
return check_static_method_call(s,n,owner_type,m);
}
}
et=check_expr(s,n->a->a);
/* A method can be reached through a reference or an owner as well
as through the value itself. */
if (et && (et->kind==FE_TYPE_REF || et->kind==FE_TYPE_OWNED) &&
et->elem && et->elem->kind==FE_TYPE_STRUCT &&
find_method(c,et->elem,n->a->b ? n->a->b->text : ""))
et=et->elem;
method=et && et->kind==FE_TYPE_STRUCT ?
find_method(c,et,n->a->b ? n->a->b->text : "") : 0;
if(method) {
FeBindSave msave;
int bound=0;
self_param=method->a ? method->a->children : 0;
if(!self_param) {
err(c,n->loc,"method requires self parameter");
return unknown(c);
}
/* A method of a generic instance reads its signature with that
instance's arguments bound. */
if (et->bind_count) {
push_instance_bindings(c,&msave,et);
bind_self(c,et);
bound=1;
}
a=method_type(c,self_param->a,et);
if(a->kind==FE_TYPE_REF && a->ref_mut &&
!lvalue_writable(s,n->a->a))
err(c,n->loc,"mutable method requires a mutable receiver");
if(a->kind!=FE_TYPE_REF) mark_moved(s,n->a->a,et);
param=self_param->next;
arg=n->children;
while(param && arg) {
a=check_expr(s,arg);
b=method_type(c,param->a,et);
/* A method argument gets the same call-only weakening a
free function's does: an exclusive view may be handed
over as a shared one for the length of the call, and an
exclusive borrow is lent rather than given. */
if(!compatible(b,a,arg) &&
!(b && a && b->kind==FE_TYPE_SLICE &&
a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut &&
fe_type_equal(b->elem,a->elem)) &&
!(b && a && b->kind==FE_TYPE_REF && a->kind==FE_TYPE_REF &&
!b->ref_mut && a->ref_mut &&
fe_type_equal(b->elem,a->elem)) &&
a->kind!=FE_TYPE_UNKNOWN)
err(c,arg->loc,"method argument type mismatch");
if(!call_reborrows(b,a) &&
!(b && a && b->kind==FE_TYPE_SLICE &&
a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut))
mark_moved(s,arg,a);
param=param->next;
arg=arg->next;
}
if(param || arg) err(c,n->loc,"wrong number of method arguments");
n->sem_decl=method;
n->sem_type=method->b ? method_type(c,method->b,et) :
fe_type_intern(&c->types,"void");
if (bound) {
pop_bindings(c,&msave);
check_instance_method(s,et,method,n->loc,n);
}
return n->sem_type;
}
if (et && (et->kind==FE_TYPE_SLICE || et->kind==FE_TYPE_STR) &&
n->a->b && n->a->b->text &&
strcmp(n->a->b->text,"trim")==0) {
if (n->children) err(c,n->loc,"trim takes no arguments");
n->sem_type=fe_type_slice(&c->types,et->elem);
return n->sem_type;
}
variant=et && et->kind==FE_TYPE_ENUM ?
fe_type_variant(et,n->a->b ? n->a->b->text : "") : 0;
arg=n->children;
if (!variant) { err(c,n->loc,"invalid enum variant constructor"); return unknown(c); }
if (variant->field_count==1 && arg) {
FeType *av=check_expr(s,arg);
if(!compatible(variant->fields[0].type,av,arg)&&av->kind!=FE_TYPE_UNKNOWN) err(c,arg->loc,"enum payload type mismatch");
} else if (variant->field_count != 0 || arg) err(c,n->loc,"wrong enum payload arity");
n->sem_type=et; return et;
}
if (n->a && n->a->kind == FE_N_IDENT) {
sym = find_symbol(s->scope, n->a->text ? n->a->text : "");
if (!sym) {
err(c, n->loc, "unknown function");
return unknown(c);
}
if (decl_is_generic(sym->fn))
return check_generic_call(s,n,sym,current_unit(c));
return check_call_args(s, n, sym, 0, 0);
}
for (x = n->children; x; x = x->next) check_expr(s, x);
return unknown(c);
}
if (n->kind == FE_N_MEMBER) {
if (is_error_set_member(s,n)) {
n->sem_type=fe_type_intern(&c->types,"core.Error");
return n->sem_type;
}
if (n->a && n->a->kind==FE_N_IDENT && n->a->text &&
strcmp(n->a->text,"io")==0 && n->b && n->b->text &&
(strcmp(n->b->text,"stdout")==0 ||
strcmp(n->b->text,"stderr")==0)) {
n->sem_type=fe_type_intern(&c->types,"io.Writer");
return n->sem_type;
}
a=check_expr(s,n->a);
if (a->kind == FE_TYPE_REF && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
n->sem_type=a->elem;
return a->elem;
}
if(a->kind==FE_TYPE_REF && a->elem &&
a->elem->kind==FE_TYPE_STRUCT) {
field=fe_type_field(a->elem,n->b ? n->b->text : "");
if(!field) { err(c,n->loc,"unknown struct field"); return unknown(c); }
n->sem_type=field->type;
return field->type;
}
if (a->kind == FE_TYPE_OWNED && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
n->sem_type=a->elem;
return a->elem;
}
if(a->kind==FE_TYPE_STRUCT) {
field=fe_type_field(a,n->b ? n->b->text : "");
if(!field) { err(c,n->loc,"unknown struct field"); return unknown(c); }
n->sem_type=field->type; return field->type;
}
if(a->kind==FE_TYPE_ENUM) {
if(!fe_type_variant(a,n->b ? n->b->text : "")) err(c,n->loc,"unknown enum variant");
n->sem_type=a; return a;
}
if((a->kind==FE_TYPE_SLICE || a->kind==FE_TYPE_STR) && n->b &&
strcmp(n->b->text,"n")==0) {
n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type;
}
return unknown(c);
}
return unknown(c);
}
/* `base_in` is the already-checked type of a member expression's base. The M7
lvalue path looks at that base before delegating here, and checking it a
second time reports any ownership violation on it a second time too. */
FeType *check_lvalue_core(FeCheckerState *s, FeNode *n, int read,
FeType *base_in)
{
FeSym *sym;
FeType *base;
FeFieldType *field;
if (n && n->kind == FE_N_IDENT) {
sym = find_symbol(s->scope, n->text ? n->text : "");
if (!sym) {
err(s->c, n->loc, "unknown name");
return unknown(s->c);
}
if (sym->fn) {
err(s->c, n->loc, "function is not assignable");
return unknown(s->c);
}
if (!sym->mutable)
err(s->c, n->loc, "cannot assign to immutable let");
n->cname = sym->cname;
n->sem_type = sym->type;
if (read) {
fe_own_access(s->c->diags,&sym->own,FE_OWN_READ,n->loc);
sym->moved=sym->own.move;
}
return sym->type;
}
if (n && n->kind == FE_N_MEMBER) {
base=base_in ? base_in : check_expr(s,n->a);
if (base && base->kind == FE_TYPE_REF && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
if (!base->ref_mut)
err(s->c,n->loc,"cannot write through shared reference");
n->sem_type=base->elem;
return base->elem;
}
if(base && base->kind==FE_TYPE_REF && base->elem &&
base->elem->kind==FE_TYPE_STRUCT) {
if(!base->ref_mut)
err(s->c,n->loc,"cannot write through shared reference");
field=fe_type_field(base->elem,n->b ? n->b->text : "");
if(!field) { err(s->c,n->loc,"assignment requires a valid struct field"); return unknown(s->c); }
n->sem_type=field->type;
return field->type;
}
if (base && base->kind == FE_TYPE_OWNED && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
n->sem_type=base->elem;
return base->elem;
}
if (!lvalue_writable(s,n->a))
err(s->c,n->loc,"cannot assign through immutable value");
field=base && base->kind==FE_TYPE_STRUCT ? fe_type_field(base,n->b ? n->b->text : "") : 0;
if(!field) { err(s->c,n->loc,"assignment requires a valid struct field"); return unknown(s->c); }
n->sem_type=field->type; return field->type;
}
if (n && n->kind == FE_N_INDEX) {
base=check_index(s,n);
if (n->a && n->a->sem_type &&
n->a->sem_type->kind == FE_TYPE_SLICE &&
!n->a->sem_type->ref_mut)
err(s->c,n->loc,"cannot write through shared slice");
else if (n->a && n->a->sem_type &&
n->a->sem_type->kind != FE_TYPE_SLICE &&
!lvalue_writable(s,n->a))
err(s->c,n->loc,"cannot assign through immutable value");
return base;
}
if (n) err(s->c, n->loc, "assignment requires a variable");
return unknown(s->c);
}
int compound_operator(const char *op)
{
return op && strcmp(op, "=") != 0;
}
+679
View File
@@ -0,0 +1,679 @@
#include "checkpri.h"
unsigned decl_type_param_count(const FeNode *decl)
{
FeNode *p;
unsigned n=0;
if (!decl) return 0;
if (decl->kind==FE_N_FN) {
for (p=decl->a?decl->a->children:0;p;p=p->next)
if (p->flags & FE_NODE_COMPTIME) ++n;
return n;
}
if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM)
for (p=decl->a?decl->a->children:0;p;p=p->next) ++n;
return n;
}
FeNode *decl_type_param(const FeNode *decl, unsigned i)
{
FeNode *p;
unsigned n=0;
if (!decl) return 0;
if (decl->kind==FE_N_FN) {
for (p=decl->a?decl->a->children:0;p;p=p->next)
if (p->flags & FE_NODE_COMPTIME) { if (n==i) return p; ++n; }
return 0;
}
for (p=decl->a?decl->a->children:0;p;p=p->next) { if (n==i) return p; ++n; }
return 0;
}
int decl_is_generic(const FeNode *decl)
{
return decl_type_param_count(decl)!=0;
}
/* SPEC 9: v0.1 has comptime type parameters and no other kind. */
void check_generic_params(FeCheck *c, FeNode *decl)
{
FeNode *p;
if (!decl || decl->kind!=FE_N_FN) return;
for (p=decl->a?decl->a->children:0;p;p=p->next) {
if (!(p->flags & FE_NODE_COMPTIME)) continue;
if (!p->a || p->a->kind!=FE_N_TYPE || !p->a->text ||
strcmp(p->a->text,"type")!=0)
err(c,p->loc,"a comptime parameter must be a type parameter");
}
}
void push_bindings(FeCheck *c, FeBindSave *save, FeNode *decl,
FeType **args, unsigned count)
{
unsigned i;
save->count=c->types.param_count;
for (i=0;i<FE_TYPE_PARAM_MAX;++i) save->params[i]=c->types.params[i];
c->types.param_count=0;
for (i=0;i<count && i<FE_TYPE_PARAM_MAX;++i) {
FeNode *p=decl_type_param(decl,i);
c->types.params[i].name=p && p->text ? p->text : "?";
c->types.params[i].type=args[i];
++c->types.param_count;
}
}
/* Restore the bindings recorded on an instance, so a method sees exactly the
environment its type was built with. */
void push_instance_bindings(FeCheck *c, FeBindSave *save, FeType *t)
{
unsigned i;
save->count=c->types.param_count;
for (i=0;i<FE_TYPE_PARAM_MAX;++i) save->params[i]=c->types.params[i];
c->types.param_count=0;
for (i=0;i<t->bind_count && i<FE_TYPE_PARAM_MAX;++i)
c->types.params[c->types.param_count++]=t->binds[i];
}
void bind_self(FeCheck *c, FeType *owner)
{
if (c->types.param_count>=FE_TYPE_PARAM_MAX) return;
c->types.params[c->types.param_count].name="Self";
c->types.params[c->types.param_count].type=owner;
++c->types.param_count;
}
void pop_bindings(FeCheck *c, const FeBindSave *save)
{
unsigned i;
for (i=0;i<FE_TYPE_PARAM_MAX;++i) c->types.params[i]=save->params[i];
c->types.param_count=save->count;
}
/* `unit.Name(arg,arg)` -- the canonical identity of one instance.
Nesting makes the readable spelling grow without bound, and a spelling that
got cut off would make two different instances look like the same one, so
past a length the arguments are written as serial numbers instead. Those are
unique, so identity stays exact even where the spelling stops being
readable. */
void instance_key(char *out, const char *unit, const char *name,
FeType **args, unsigned count)
{
unsigned i;
unsigned long n=0;
unsigned long cap=(unsigned long)FE_GENERIC_NAME_READABLE;
const char *p;
char number[24];
int readable=1;
for (p=unit?unit:"";*p;++p) { if (n<cap) out[n++]=*p; else readable=0; }
if (n<cap) out[n++]='.'; else readable=0;
for (p=name?name:"?";*p;++p) { if (n<cap) out[n++]=*p; else readable=0; }
if (n<cap) out[n++]='('; else readable=0;
for (i=0;i<count && readable;++i) {
if (i) { if (n<cap) out[n++]=','; else { readable=0; break; } }
for (p=args[i] && args[i]->name[0] ? args[i]->name : "?";*p;++p) {
if (n<cap) out[n++]=*p;
else { readable=0; break; }
}
}
if (readable && n<cap) out[n++]=')'; else readable=0;
if (readable) { out[n]='\0'; return; }
n=0;
for (p=unit?unit:"";*p && n<cap;++p) out[n++]=*p;
if (n<cap) out[n++]='.';
for (p=name?name:"?";*p && n<cap;++p) out[n++]=*p;
if (n<cap) out[n++]='(';
for (i=0;i<count;++i) {
if (i && n<cap) out[n++]=',';
sprintf(number,"#%u",args[i] ? args[i]->serial : 0U);
for (p=number;*p && n<cap;++p) out[n++]=*p;
}
if (n<cap) out[n++]=')';
out[n]='\0';
}
/* Already built, or being built right now. Re-asking for a pending instance is
how a recursive generic terminates, so it must not look like a new one. */
const char *instance_cname(FeCheck *c, const char *key)
{
unsigned i;
for (i=0;i<c->instance_count;++i)
if (!strcmp(c->instances[i].key,key)) return c->instances[i].cname;
return 0;
}
int instance_known(FeCheck *c, const char *key)
{
unsigned i;
for (i=0;i<c->instance_count;++i)
if (strcmp(c->instances[i].key,key)==0) return 1;
return 0;
}
int instance_record(FeCheck *c, const char *key, FeLoc loc,
FeNode *decl, FeUnit *home, FeType *owner)
{
FeInstance *inst;
unsigned i;
if (instance_known(c,key)) return 0;
if (c->instance_count>=FE_GENERIC_INSTANCE_MAX) {
err(c,loc,"too many generic instances");
return -1;
}
inst=&c->instances[c->instance_count];
strcpy(inst->key,key);
inst->decl=decl;
inst->home=home ? home->name : 0;
inst->owner=owner;
inst->cname=unit_cname(c,key);
/* The bindings in force right now are the ones this instance was built
with, and lowering has to see exactly those again. */
inst->bind_count=c->types.param_count;
for (i=0;i<c->types.param_count && i<FE_TYPE_PARAM_MAX;++i)
inst->binds[i]=c->types.params[i];
++c->instance_count;
return 1;
}
/* One step further down a chain of instantiations. Chains that keep producing
new instances are the ones that never end, so the limit counts nesting. */
int instance_descend(FeCheck *c, FeLoc loc)
{
if (c->instance_depth>=FE_GENERIC_DEPTH_MAX) {
err(c,loc,"generic instantiation depth exceeded");
return 0;
}
++c->instance_depth;
return 1;
}
FeUnit *current_unit(FeCheck *c)
{
unsigned u;
for (u=0;u<c->build->count;++u)
if (strcmp(c->build->units[u].name,c->types.unit_name)==0)
return &c->build->units[u];
return c->unit;
}
/* Build `Box(i32)`: the declaration's fields with the parameters bound, under
a name that records which arguments made it. */
FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl,
const char *key, FeType **args,
unsigned count)
{
FeBindSave save;
FeType *t;
FeNode *f;
unsigned fields=0;
unsigned i=0;
t=fe_type_intern_unit(&c->types,home->name,key);
if (!t || t->kind!=FE_TYPE_UNKNOWN) return t;
t->kind=FE_TYPE_STRUCT;
t->building=1;
t->packed=(decl->flags & FE_NODE_PACKED)!=0;
t->decl_node=decl;
t->bind_count=0;
for (i=0;i<count && i<FE_TYPE_PARAM_MAX;++i) {
FeNode *p=decl_type_param(decl,i);
t->binds[t->bind_count].name=p && p->text ? p->text : "?";
t->binds[t->bind_count].type=args[i];
++t->bind_count;
}
t->cname=unit_cname(c,key);
for (f=decl->children;f;f=f->next)
if (f->kind==FE_N_FN && f->text && strcmp(f->text,"drop")==0)
t->has_drop=1;
for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) ++fields;
t->field_count=fields;
if (fields) {
const char *save_unit=c->types.unit_name;
t->fields=(FeFieldType *)fe_arena_alloc(&c->arena,
fields*sizeof(FeFieldType));
if (!t->fields) { t->field_count=0; return t; }
/* Field types are written in the unit that declared the struct, not in
whichever unit asked for this instance. */
c->types.unit_name=home->name;
push_instance_bindings(c,&save,t);
bind_self(c,t);
i=0;
for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) {
t->fields[i].name=f->text;
t->fields[i].type=node_type(c,f->a);
t->fields[i].offset=0;
t->fields[i].ast_node=f;
++i;
}
pop_bindings(c,&save);
c->types.unit_name=save_unit;
}
t->building=0;
fe_type_layout_all(&c->types);
/* A type that says how to let go of itself needs that method to exist for
every instance, whether or not anyone calls it by name: scope cleanup
will. */
{
FeNode *release;
for (release=decl->children;release;release=release->next)
if (release->kind==FE_N_FN && release->text &&
!strcmp(release->text,"drop") && release->c) {
FeCheckerState s;
memset(&s,0,sizeof s);
s.c=c;
s.scope=c->unit_scope[unit_index(c,home)];
s.globals=s.scope;
check_instance_method(&s,t,release,decl->loc,0);
break;
}
}
return t;
}
FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name,
FeType **args, unsigned count, FeLoc loc)
{
FeNode *decl=unit_type_decl(c,home,name);
char key[FE_GENERIC_KEY_MAX];
if (!decl || !decl_is_generic(decl)) {
err(c,loc,"type does not take generic arguments");
return unknown(c);
}
if (decl->kind!=FE_N_STRUCT) {
err(c,loc,"only a generic struct can be instantiated");
return unknown(c);
}
if (count!=decl_type_param_count(decl)) {
err(c,loc,"wrong number of generic arguments");
return unknown(c);
}
instance_key(key,home->name,name,args,count);
if (instance_record(c,key,loc,decl,home,0)<0) return unknown(c);
return build_struct_instance(c,home,decl,key,args,count);
}
/* `Name(args...)` written in type position. */
FeType *instantiate_type_node(void *owner, const FeNode *node)
{
FeCheck *c=(FeCheck *)owner;
FeUnit *home=current_unit(c);
const char *name=node->text;
FeNode *arg;
FeType *args[FE_TYPE_PARAM_MAX];
unsigned count=0;
FeType *result;
/* `binding.Name` names a type in another unit. The binding is not itself a
type, so it has to be peeled off before anything is looked up. */
if (node->a && node->a->kind==FE_N_IDENT && node->a->text && c->build &&
c->unit) {
FeUnit *bound=fe_build_binding(c->build,c->unit,node->text);
if (bound) { home=bound; name=node->a->text; }
}
if (!node->children) {
FeNode *decl=unit_type_decl(c,home,name ? name : "");
if (decl && decl_is_generic(decl)) {
/* A generic declaration is not a type until it has arguments. */
err(c,node->loc,"generic type requires type arguments");
return unknown(c);
}
if (name!=node->text) {
FeType *there=unit_type(c,home,name);
if (there) return there;
}
return fe_type_intern(&c->types,name);
}
if (!instance_descend(c,node->loc)) return unknown(c);
for (arg=node->children;arg;arg=arg->next) {
if (count<FE_TYPE_PARAM_MAX)
args[count]=fe_type_from_ast(&c->types,arg);
++count;
}
if (count>FE_TYPE_PARAM_MAX) {
err(c,node->loc,"wrong number of generic arguments");
--c->instance_depth;
return unknown(c);
}
result=instantiate_struct(c,home,name ? name : "",args,count,
node->loc);
--c->instance_depth;
return result;
}
/* A type written where an expression is: `i32`, `Box(i32)`. Only a comptime
argument position accepts one. */
FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok)
{
FeCheck *c=s->c;
FeType *t;
unsigned i;
*ok=0;
if (!n) return unknown(c);
if (n->kind==FE_N_IDENT && n->text) {
for (i=0;i<c->types.param_count;++i)
if (strcmp(c->types.params[i].name,n->text)==0) {
*ok=1;
return c->types.params[i].type;
}
if (find_symbol(s->scope,n->text)) {
/* A const alias of a type is that type (SPEC 4.7). */
FeSym *sym=find_symbol(s->scope,n->text);
if (sym && sym->decl && sym->decl->kind==FE_N_CONST &&
sym->decl->b && sym->decl->b->kind==FE_N_IDENT)
return type_from_expr(s,sym->decl->b,ok);
return unknown(c);
}
t=fe_type_intern(&c->types,n->text);
if (t && t->kind!=FE_TYPE_UNKNOWN) { *ok=1; return t; }
return unknown(c);
}
/* `binding.Name` names a type in another unit. */
if (n->kind==FE_N_MEMBER && n->a && n->a->kind==FE_N_IDENT &&
n->b && n->b->text) {
FeUnit *bound=binding_unit(s,n->a);
if (bound) {
FeType *there=unit_type(c,bound,n->b->text);
if (there) { *ok=1; return there; }
}
return unknown(c);
}
if (n->kind==FE_N_CALL && n->a &&
(n->a->kind==FE_N_IDENT ||
(n->a->kind==FE_N_MEMBER && n->a->a &&
n->a->a->kind==FE_N_IDENT && n->a->b && n->a->b->text))) {
FeType *args[FE_TYPE_PARAM_MAX];
unsigned count=0;
FeNode *arg;
FeType *result;
FeUnit *home=current_unit(c);
const char *want;
/* `Name(args)` here, `binding.Name(args)` when the declaration is in
another unit. */
if (n->a->kind==FE_N_MEMBER) {
FeUnit *bound=binding_unit(s,n->a->a);
if (!bound) return unknown(c);
home=bound;
want=n->a->b->text;
} else {
want=n->a->text;
}
if (!want || !unit_type_decl(c,home,want)) return unknown(c);
if (!instance_descend(c,n->loc)) { *ok=1; return unknown(c); }
for (arg=n->children;arg;arg=arg->next) {
int inner=0;
if (count<FE_TYPE_PARAM_MAX)
args[count]=type_from_expr(s,arg,&inner);
if (!inner) { --c->instance_depth; return unknown(c); }
++count;
}
if (count>FE_TYPE_PARAM_MAX) { --c->instance_depth; return unknown(c); }
result=instantiate_struct(c,home,want,args,count,n->loc);
--c->instance_depth;
*ok=1;
return result;
}
return unknown(c);
}
/* Can this initializer be worked out before the program runs?
A global's bytes go into the image, so there is no moment at which a call in
its initializer could happen -- the emitter had been quietly dropping the
work and leaving zeros. Anything that is a name for a value already known is
fine; anything that is work is not. */
int const_foldable(FeCheckerState *s, FeNode *n)
{
FeNode *x;
if (!n) return 1;
switch (n->kind) {
case FE_N_LITERAL:
return 1;
case FE_N_IDENT: {
/* Another `const` is a name for a value; a `static`/`var` is storage
that does not exist yet. */
FeSym *sym=find_symbol(s->scope,n->text ? n->text : "");
return sym && sym->decl && sym->decl->kind==FE_N_CONST;
}
case FE_N_MEMBER:
/* `E.Variant`, `error.Name`, `unit.CONST` -- a name, not work. */
if (n->a && n->a->kind==FE_N_IDENT) return 1;
return const_foldable(s,n->a);
case FE_N_UNARY:
if (n->text && strcmp(n->text,"try")==0) return 0;
return const_foldable(s,n->a);
case FE_N_BINARY:
if (n->text && (strcmp(n->text,"catch")==0 ||
strcmp(n->text,"orelse")==0)) return 0;
return const_foldable(s,n->a) && const_foldable(s,n->b);
case FE_N_TYPE:
return const_foldable(s,n->a);
case FE_N_EXPR:
return const_foldable(s,n->a);
case FE_N_STRUCT_INIT:
case FE_N_ARRAY_INIT:
for (x=n->children;x;x=x->next)
if (!const_foldable(s,x->kind==FE_N_FIELD ? x->a : x)) return 0;
return 1;
default:
return 0;
}
}
/* A `comptime if` condition. Only the forms SPEC 9 allows: type equality and
the type predicates. Anything else is not decidable here. */
int comptime_condition(FeCheckerState *s, FeNode *n, int *out)
{
FeType *a;
FeType *b;
int ok=0;
int eq;
if (!n) return 0;
if (n->kind==FE_N_BINARY && n->text &&
(strcmp(n->text,"==")==0 || strcmp(n->text,"!=")==0)) {
a=type_from_expr(s,n->a,&ok);
if (!ok) return 0;
b=type_from_expr(s,n->b,&ok);
if (!ok) return 0;
eq=fe_type_equal(a,b);
*out=strcmp(n->text,"==")==0 ? eq : !eq;
return 1;
}
if (n->kind==FE_N_CALL && n->text &&
(strcmp(n->text,"@is_int")==0 || strcmp(n->text,"@is_ptr")==0)) {
a=type_from_expr(s,n->children,&ok);
if (!ok) return 0;
*out=strcmp(n->text,"@is_int")==0 ? fe_type_is_integer(a) :
(a && (a->kind==FE_TYPE_OWNED || a->kind==FE_TYPE_REF));
return 1;
}
return 0;
}
/* Check a generic body once, in the unit that declared it and with the
instance's arguments bound. Errors land on the operation that is wrong; the
call site gets a note, because the call is context and not the defect. */
void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl,
FeType *owner, FeBindSave *bindings, FeLoc site)
{
FeAst *save_ast=c->ast;
FeUnit *save_unit=c->unit;
const char *save_name=c->types.unit_name;
unsigned before=c->diags->errors;
(void)bindings;
c->ast=&home->ast;
c->unit=home;
c->types.unit_name=home->name;
fe_diags_source(c->diags,home->source,home->size);
if (owner) check_method(c,decl,c->unit_scope[unit_index(c,home)],owner);
else check_fn(c,decl,c->unit_scope[unit_index(c,home)]);
c->ast=save_ast;
c->unit=save_unit;
c->types.unit_name=save_name;
if (save_unit) fe_diags_source(c->diags,save_unit->source,save_unit->size);
if (c->diags->errors>before)
fe_diag_note_src(c->diags,site,"instantiated here");
}
/* A call to a generic function: read the type arguments, check the value
arguments against the bound signature, then check the body once. */
FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym,
FeUnit *home)
{
FeCheck *c=s->c;
FeNode *decl=sym->fn;
unsigned want=decl_type_param_count(decl);
FeType *args[FE_TYPE_PARAM_MAX];
FeNode *arg=n->children;
unsigned i;
char key[FE_GENERIC_KEY_MAX];
FeBindSave save;
FeType *result;
int fresh;
if (want>FE_TYPE_PARAM_MAX) {
err(c,n->loc,"too many generic parameters");
return unknown(c);
}
for (i=0;i<want;++i) {
int ok=0;
if (!arg) {
err(c,n->loc,"generic call requires explicit type arguments");
return unknown(c);
}
args[i]=type_from_expr(s,arg,&ok);
if (!ok) {
err(c,arg->loc,"a comptime type argument must name a type");
return unknown(c);
}
arg=arg->next;
}
instance_key(key,home->name,decl->text,args,want);
push_bindings(c,&save,decl,args,want);
result=check_call_args(s,n,sym,home->name,want);
fresh=instance_record(c,key,n->loc,decl,home,0);
pop_bindings(c,&save);
/* The call goes to this instance, not to the declaration it came from. */
if (n->a) n->a->cname=(char *)instance_cname(c,key);
if (fresh>0) {
if (!instance_descend(c,n->loc)) return result;
push_bindings(c,&save,decl,args,want);
instantiate_body(c,home,decl,0,&save,n->loc);
pop_bindings(c,&save);
--c->instance_depth;
}
return result;
}
/* `Type.method(...)` where Type is a generic instance and the method takes no
self parameter. */
/* The unit a name belongs to, by name. */
FeUnit *unit_named(FeCheck *c, const char *name)
{
unsigned u;
if (!name) return 0;
for (u=0;u<c->build->count;++u)
if (!strcmp(c->build->units[u].name,name)) return &c->build->units[u];
return 0;
}
FeType *check_static_method_call(FeCheckerState *s, FeNode *n,
FeType *owner, FeNode *method)
{
FeCheck *c=s->c;
/* A method belongs to the unit that declared its type, not to whichever
unit happens to be calling it. */
FeUnit *home=unit_named(c,owner ? owner->unit : 0);
FeBindSave save;
FeType *result;
char key[FE_GENERIC_KEY_MAX];
FeType *self_args[1];
int fresh;
FeSym fake;
if (!home) home=current_unit(c);
self_args[0]=owner;
instance_key(key,home->name,method->text,self_args,1);
memset(&fake,0,sizeof fake);
fake.name=method->text;
fake.cname=method->cname;
fake.fn=method;
fake.decl=method;
push_instance_bindings(c,&save,owner);
bind_self(c,owner);
result=check_call_args(s,n,&fake,home->name,0);
fresh=instance_record(c,key,n->loc,method,home,owner);
pop_bindings(c,&save);
if (n->a) n->a->cname=(char *)instance_cname(c,key);
if (fresh>0) {
if (!instance_descend(c,n->loc)) return result;
push_instance_bindings(c,&save,owner);
bind_self(c,owner);
instantiate_body(c,home,method,owner,&save,n->loc);
pop_bindings(c,&save);
--c->instance_depth;
}
return result;
}
/* The body of a method on a generic instance, checked once per instance. */
void check_instance_method(FeCheckerState *s, FeType *owner,
FeNode *method, FeLoc site, FeNode *call)
{
FeCheck *c=s->c;
/* A method belongs to the unit that declared its type, not to whichever
unit happens to be calling it. */
FeUnit *home=unit_named(c,owner ? owner->unit : 0);
FeBindSave save;
char key[FE_GENERIC_KEY_MAX];
FeType *self_args[1];
self_args[0]=owner;
if (!home) home=current_unit(c);
instance_key(key,home->name,method->text,self_args,1);
{
FeBindSave probe;
int fresh;
push_instance_bindings(c,&probe,owner);
bind_self(c,owner);
fresh=instance_record(c,key,site,method,home,owner);
pop_bindings(c,&probe);
/* The call names this instance's copy of the method. */
if (call && call->a) call->a->cname=(char *)instance_cname(c,key);
if (fresh<=0) return;
}
if (!instance_descend(c,site)) return;
push_instance_bindings(c,&save,owner);
bind_self(c,owner);
instantiate_body(c,home,method,owner,&save,site);
pop_bindings(c,&save);
--c->instance_depth;
}
/* SPEC 4.7: `const Word = i32;` is another spelling of a type, not a value.
It has no initializer to check and no storage. */
int const_names_type(FeCheckerState *s, FeNode *n)
{
FeType *t;
if (!n->b || n->b->kind!=FE_N_IDENT || !n->b->text) return 0;
if (n->a) return 0;
if (find_symbol(s->globals,n->b->text)) return 0;
t=fe_type_intern(&s->c->types,n->b->text);
return t && t->kind!=FE_TYPE_UNKNOWN;
}
FeNode *type_method(FeType *t, const char *name)
{
FeNode *m;
if (!t || !t->decl_node || !name) return 0;
for (m=t->decl_node->children;m;m=m->next)
if (m->kind==FE_N_FN && m->text && strcmp(m->text,name)==0) return m;
return 0;
}
int method_is_static(const FeNode *method)
{
FeNode *first=method && method->a ? method->a->children : 0;
return !first || !first->text || strcmp(first->text,"self")!=0;
}
/* A call to a named function. `home` is the unit the signature was written in,
null when that is the unit being checked: parameter and return types have to
be read where they were written or a name would mean the caller's type. */
/* `error.Name` is a member of the default error set. That set is open -- names
are collected across the build and numbered later, not declared -- so any
name is well formed here and the value's type is core.Error. */
+266
View File
@@ -0,0 +1,266 @@
#ifndef FE_CHECKPRI_H
#define FE_CHECKPRI_H
/* The checker's own vocabulary, shared by the files it is split across.
Nothing outside the checker includes this. */
#include "check.h"
#include "m7.h"
#include <stdlib.h>
#define FE_M7_FLOW_CAP 64U
#include "own.h"
#include <string.h>
#include <stdio.h>
typedef struct FeSym FeSym;
/* FeScope is forward declared in check.h. */
struct FeSym {
const char *name;
char *cname;
FeType *type;
FeNode *fn;
int mutable;
int initialized;
int moved;
FeNode *decl;
/* M6 ownership is tracked at the root local/parameter. A reference
binding remembers that root so releasing the binding's last use can
release the root borrow without a separate alias engine. */
FeOwnState own;
FeSym *borrow_root;
/* Which field of the root this binding borrowed, or null for all of it. */
const char *borrow_field;
int borrow_mut;
int borrow_defer;
FeScope *owner;
};
struct FeScope {
FeScope *parent;
FeSym *items;
unsigned count;
unsigned capacity;
};
typedef struct FeCheckerState {
FeCheck *c;
FeScope *scope;
FeScope *globals;
FeType *ret;
unsigned loop_depth;
unsigned defer_depth;
/* SPEC 5 R9 lists what only `unsafe` allows; `asm` is on it. */
unsigned unsafe_depth;
FeOwnLiveness liveness;
FeNode *fn_node;
/* While a projection is being checked, which field of which base it
reaches. The read happens down at the identifier, which cannot see the
chain above it, so the chain leaves word here on the way down. */
const char *proj_field;
FeNode *proj_base;
} FeCheckerState;
/* The type bindings in force, saved across a nested instantiation. */
typedef struct FeBindSave {
FeTypeBind params[FE_TYPE_PARAM_MAX];
unsigned count;
} FeBindSave;
typedef struct FeFlowSlot {
FeSym *sym;
int moved;
int initialized;
int own_move;
int own_initialized;
} FeFlowSlot;
typedef struct FeFlowBorrow {
FeSym *root;
const char *field;
int mutable;
} FeFlowBorrow;
/* How long a chain of new generic instances may get, and how long an
instance's readable spelling may be before it falls back to serials. */
#define FE_GENERIC_DEPTH_MAX 32
#define FE_GENERIC_NAME_READABLE 200
/* Every definition in the checker, so the split files can see each other. */
FeType *unknown(FeCheck *c);
void err(FeCheck *c, FeLoc loc, const char *msg);
int ordered_type(const FeType *t);
int known(FeType *t);
int in_own_drop(FeCheckerState *s, FeNode *n);
void mark_moved(FeCheckerState *s, FeNode *n, FeType *t);
int compatible(FeType *want, FeType *got, FeNode *value);
int call_reborrows(const FeType *param, const FeType *arg);
int return_weakens(const FeType *want, const FeType *got);
int explicit_castable(FeType *a, FeType *b);
FeType *node_type(FeCheck *c, FeNode *n);
char *unit_cname(FeCheck *c, const char *name);
char *local_cname(FeCheck *c, const char *name);
FeScope *scope_new(FeCheckerState *s, FeScope *parent);
FeSym *find_current(FeScope *scope, const char *name);
FeSym *find_symbol(FeScope *scope, const char *name);
FeSym *add_symbol(FeCheckerState *s, FeScope *scope,
const char *name, FeType *type, FeNode *fn,
int mutable, int initialized, char *cname,
FeNode *decl);
void enter_unit(FeCheck *c, unsigned index);
unsigned unit_index(FeCheck *c, const FeUnit *u);
FeUnit *binding_unit(FeCheckerState *s, FeNode *base);
int decl_is_public(const FeNode *decl);
FeSym *unit_member(FeCheck *c, FeUnit *u, const char *name);
FeType *unit_type(FeCheck *c, FeUnit *u, const char *name);
int enter_declaring_unit(FeCheck *c, const char *unit_name);
FeNode *unit_type_decl(FeCheck *c, FeUnit *u, const char *name);
FeType *node_type_in(FeCheck *c, const char *unit, FeNode *node);
FeNode *find_method(FeCheck *c, FeType *owner, const char *name);
FeType *method_type(FeCheck *c, FeNode *node, FeType *owner);
unsigned flow_capture(FeScope *scope, FeFlowSlot *slots, unsigned cap);
void flow_restore(FeFlowSlot *slots, unsigned count);
void flow_merge(FeFlowSlot *base, FeFlowSlot *left, FeFlowSlot *right,
unsigned count);
FeSym *own_root_symbol(FeCheckerState *s, FeNode *expr);
const char *own_projected_field(FeNode *expr, FeNode **root_out);
int own_is_global(FeCheckerState *s, FeSym *sym);
void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable);
void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr);
FeSym *own_derived_call_root(FeCheckerState *s, FeNode *call);
void own_bind_derived_call(FeCheckerState *s, FeSym *binding,
FeNode *value);
int own_stmt_uses(FeNode *node, const char *name);
int own_defer_uses(FeNode *node, const char *name);
int own_contains_node(FeNode *node, FeNode *needle);
void own_release_after_stmt(FeCheckerState *s, FeScope *scope,
FeNode *stmt, int scope_end);
FeOwnState *flow_own_new(FeCheckerState *s, unsigned count);
void flow_own_capture(FeFlowSlot *slots, FeOwnState *states,
unsigned count);
void flow_own_restore(FeFlowSlot *slots, FeOwnState *states,
unsigned count);
void flow_own_merge(FeFlowSlot *slots, FeOwnState *left,
FeOwnState *right, unsigned count);
FeFlowBorrow *flow_borrow_new(FeCheckerState *s, unsigned count);
void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states,
unsigned count);
void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states,
unsigned count);
void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left,
FeFlowBorrow *right, unsigned count);
FeNode *find_const_node(FeCheck *c, const char *name);
const char *builtin_format(FeCheckerState *s, FeNode *fmt);
int format_is_slice_u8(FeType *t);
int format_is_writer_type(FeType *t);
int format_arg_ok(FeType *t, int verb);
void check_format_call(FeCheckerState *s, FeNode *n);
int is_format_builtin(const char *name);
int lvalue_writable(FeCheckerState *s, FeNode *n);
int has_field(FeNode *list, const char *name);
int field_is_visible(FeCheckerState *s, const FeType *t,
const FeFieldType *field);
FeType *check_struct_fields(FeCheckerState *s, FeNode *n, FeType *t);
FeType *check_struct_init(FeCheckerState *s, FeNode *n);
FeType *check_array_init(FeCheckerState *s, FeNode *n);
int array_slice_lvalue(FeNode *n);
FeType *check_index(FeCheckerState *s, FeNode *n);
FeType *check_identifier(FeCheckerState *s, FeNode *n);
FeType *check_expr_core(FeCheckerState *s, FeNode *n);
FeType *check_lvalue_core(FeCheckerState *s, FeNode *n, int read,
FeType *base_in);
int compound_operator(const char *op);
void check_match(FeCheckerState *s, FeNode *n);
void check_for(FeCheckerState *s, FeNode *n);
void check_type_cycle(FeCheck *c, FeType *t);
void check_type_cycles(FeCheck *c);
int own_ast_reference_type(FeNode *type);
int own_ast_pointer_to_reference(FeNode *type);
void check_reference_storage(FeCheck *c, FeNode *decl);
int own_return_from_allowed_root(FeCheckerState *s, FeNode *expr);
void check_stmt_core(FeCheckerState *s, FeNode *n);
void check_fn(FeCheck *c, FeNode *fn, FeScope *globals);
void check_method(FeCheck *c, FeNode *fn, FeScope *globals,
FeType *owner);
int m7_actual_compatible(FeType *want, FeType *got, FeNode *value);
FeType *m7_check_expected(FeCheckerState *s, FeNode *value,
FeType *expected);
FeType *m7_member_field(FeCheckerState *s, FeNode *n, FeType *base);
int m7_place_is_projection(FeNode *n);
unsigned decl_type_param_count(const FeNode *decl);
FeNode *decl_type_param(const FeNode *decl, unsigned i);
int decl_is_generic(const FeNode *decl);
void check_generic_params(FeCheck *c, FeNode *decl);
void push_bindings(FeCheck *c, FeBindSave *save, FeNode *decl,
FeType **args, unsigned count);
void push_instance_bindings(FeCheck *c, FeBindSave *save, FeType *t);
void bind_self(FeCheck *c, FeType *owner);
void pop_bindings(FeCheck *c, const FeBindSave *save);
void instance_key(char *out, const char *unit, const char *name,
FeType **args, unsigned count);
const char *instance_cname(FeCheck *c, const char *key);
int instance_known(FeCheck *c, const char *key);
int instance_record(FeCheck *c, const char *key, FeLoc loc,
FeNode *decl, FeUnit *home, FeType *owner);
int instance_descend(FeCheck *c, FeLoc loc);
FeUnit *current_unit(FeCheck *c);
FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl,
const char *key, FeType **args,
unsigned count);
FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name,
FeType **args, unsigned count, FeLoc loc);
FeType *instantiate_type_node(void *owner, const FeNode *node);
FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok);
int comptime_condition(FeCheckerState *s, FeNode *n, int *out);
int const_foldable(FeCheckerState *s, FeNode *n);
void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl,
FeType *owner, FeBindSave *bindings, FeLoc site);
FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym,
FeUnit *home);
FeUnit *unit_named(FeCheck *c, const char *name);
FeType *check_static_method_call(FeCheckerState *s, FeNode *n,
FeType *owner, FeNode *method);
void check_instance_method(FeCheckerState *s, FeType *owner,
FeNode *method, FeLoc site, FeNode *call);
int const_names_type(FeCheckerState *s, FeNode *n);
FeNode *type_method(FeType *t, const char *name);
int method_is_static(const FeNode *method);
int is_error_set_member(FeCheckerState *s, FeNode *n);
FeType *cross_unit_value(FeCheckerState *s, FeNode *n, int *handled);
FeType *check_call_args(FeCheckerState *s, FeNode *n, FeSym *sym,
const char *home, unsigned skip);
FeType *check_call(FeCheckerState *s, FeNode *n);
void m7_capture_flow(FeCheckerState *s, FeFlowSlot *slots,
FeOwnState **own, FeFlowBorrow **borrow,
unsigned *count);
void m7_restore_flow(FeFlowSlot *slots, FeOwnState *own,
FeFlowBorrow *borrow, unsigned count);
void m7_merge_rhs_flow(FeCheckerState *s, FeFlowSlot *base,
FeOwnState *own_base,
FeFlowBorrow *borrow_base,
unsigned count, FeFlowSlot *rhs,
FeOwnState *own_rhs,
FeFlowBorrow *borrow_rhs);
int m7_stmt_definitely_exits(FeNode *n);
FeType *m7_check_lazy(FeCheckerState *s, FeNode *n,
FeM7LazyKind kind);
FeType *check_expr(FeCheckerState *s, FeNode *n);
FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read);
FeType *m7_pattern_binding_type(FeCheckerState *s, FeType *payload,
FeNode *source, int *borrow_mut);
void m7_check_if_let(FeCheckerState *s, FeNode *n);
void m7_check_optional_match(FeCheckerState *s, FeNode *n,
FeType *opt);
void m7_check_match_stmt(FeCheckerState *s, FeNode *n);
void m7_check_decl_stmt(FeCheckerState *s, FeNode *n, int mutable);
void check_stmt(FeCheckerState *s, FeNode *n);
int m7_ast_reference_storage(FeNode *type);
void m7_check_storage(FeCheck *c, FeNode *decl);
void m7_validate_error_decl(FeCheck *c, FeNode *decl);
void declare_unit(FeCheck *c);
FeScope *declare_unit_scope(FeCheck *c, FeCheckerState *s);
void check_unit_bodies(FeCheck *c, FeCheckerState *s);
#endif
+188
View File
@@ -0,0 +1,188 @@
#include "checkpri.h"
int m7_ast_reference_storage(FeNode *type)
{
if (!type || !type->text) return 0;
if (strcmp(type->text,"&")==0 || strcmp(type->text,"&mut")==0 ||
(strcmp(type->text,"[")==0 && !type->a) ||
strcmp(type->text,"str")==0)
return 1;
if (strcmp(type->text,"?")==0)
return m7_ast_reference_storage(type->a);
if (strcmp(type->text,"^")==0) return 0;
return 0;
}
void m7_check_storage(FeCheck *c, FeNode *decl)
{
FeNode *m;
if (!decl) return;
if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) {
/* This pass exists for the shapes the other one cannot see, such as a
reference behind an optional. A plain `&T` field is seen by both, so
leave that one to check_reference_storage below. */
for (m=decl->children;m;m=m->next)
if (m->kind==FE_N_FIELD && m7_ast_reference_storage(m->a) &&
!own_ast_reference_type(m->a) &&
!own_ast_pointer_to_reference(m->a))
err(c,m->loc,"reference type is not allowed in aggregate storage");
}
check_reference_storage(c,decl);
}
void m7_validate_error_decl(FeCheck *c, FeNode *decl)
{
FeNode *a;
FeNode *b;
unsigned long code;
unsigned long other;
if (!decl || decl->kind!=FE_N_ERROR_DECL) return;
for (a=decl->children;a;a=a->next) {
if (!a->a || a->a->kind!=FE_N_LITERAL || !a->a->text) continue;
code=strtoul(a->a->text,0,0);
if (code==0UL)
err(c,a->loc,"error code 0 is reserved for success");
for (b=decl->children;b && b!=a;b=b->next) {
if (a->text && b->text && strcmp(a->text,b->text)==0) {
err(c,a->loc,"duplicate error member name");
break;
}
if (b->a && b->a->kind==FE_N_LITERAL && b->a->text) {
other=strtoul(b->a->text,0,0);
if (other==code) {
err(c,a->loc,"duplicate error numeric code");
break;
}
}
}
}
}
/* Everything a unit declares, before any body anywhere is looked at. */
void declare_unit(FeCheck *c)
{
FeNode *n;
/* A generic declaration is not a type; only its instances are. */
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_STRUCT && !decl_is_generic(n))
fe_type_declare_struct(&c->types,n,(n->flags & FE_NODE_PACKED)!=0);
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) {
FeNode *m;
m7_check_storage(c,n);
if (n->kind==FE_N_ERROR_DECL) m7_validate_error_decl(c,n);
check_generic_params(c,n);
for (m=n->kind==FE_N_STRUCT ? n->children : 0;m;m=m->next)
if (m->kind==FE_N_FN) check_generic_params(c,m);
}
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_ENUM && !decl_is_generic(n))
fe_type_declare_enum(&c->types,n);
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_ERROR_DECL) fe_type_declare_error(&c->types,n);
}
/* The unit's top-level names, in a scope of their own so that another unit
can look into it later without inheriting anything else. */
FeScope *declare_unit_scope(FeCheck *c, FeCheckerState *s)
{
FeNode *n;
FeNode *m;
FeType *t;
FeScope *globals;
char method_name[128];
globals=scope_new(s,0);
s->scope=globals;
s->globals=globals;
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next) {
if (n->kind==FE_N_STRUCT) {
for (m=n->children;m;m=m->next) if (m->kind==FE_N_FN) {
sprintf(method_name,"%s_%s",n->text ? n->text : "Type",
m->text ? m->text : "method");
m->cname=unit_cname(c,method_name);
}
}
if (n->kind==FE_N_GLOBAL || n->kind==FE_N_CONST) {
t=n->a ? node_type(c,n->a) : unknown(c);
add_symbol(s,globals,n->text,t,0,n->kind==FE_N_GLOBAL,
n->b!=0,unit_cname(c,n->text ? n->text : "global"),n);
}
}
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_FN) {
t=fe_type_intern(&c->types,"<fn>");
/* `extern "c"` means the linker already knows this name, so it is
not decorated with the unit it was declared in. */
add_symbol(s,globals,n->text,t,n,0,1,
(n->flags & FE_NODE_EXTERN) && n->text ? n->text :
unit_cname(c,n->text ? n->text : "fn"),n);
}
return globals;
}
void check_unit_bodies(FeCheck *c, FeCheckerState *s)
{
FeNode *n;
FeNode *m;
FeSym *sym;
FeType *t;
FeType *iv;
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_GLOBAL || n->kind==FE_N_CONST) {
sym=find_current(s->globals,n->text ? n->text : "");
if (n->kind==FE_N_CONST && const_names_type(s,n)) continue;
if (n->b) {
if (!const_foldable(s,n->b))
err(c,n->b->loc,
"a global initializer must be known at compile time");
iv=m7_check_expected(s,n->b,sym ? sym->type : 0);
if (sym && sym->type->kind==FE_TYPE_UNKNOWN) {
sym->type=iv;
n->sem_type=iv;
} else if (sym && !fe_type_equal(sym->type,iv) &&
!m7_actual_compatible(sym->type,iv,n->b))
err(c,n->loc,"global initializer type mismatch");
}
}
/* A generic body means nothing until its parameters are bound, so it is
checked once per instance and not here. */
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_FN && !decl_is_generic(n)) check_fn(c,n,s->globals);
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
if (n->kind==FE_N_STRUCT && !decl_is_generic(n)) {
t=fe_type_intern(&c->types,n->text);
for (m=n->children;m;m=m->next)
if (m->kind==FE_N_FN) check_method(c,m,s->globals,t);
}
}
int fe_check_program(FeCheck *c)
{
FeCheckerState s;
unsigned u;
s.c=c;
s.scope=0;
s.globals=0;
s.ret=fe_type_intern(&c->types,"void");
s.loop_depth=0;
s.defer_depth=0;
s.unsafe_depth=0;
s.fn_node=0;
fe_own_liveness_init(&s.liveness,&c->arena);
for (u=0;u<c->build->count;++u) { enter_unit(c,u); declare_unit(c); }
/* Only now: a field may name a type in a unit that had not declared it
yet, and resolving it early would freeze the wrong answer in place. */
for (u=0;u<c->build->count;++u) { enter_unit(c,u); check_type_cycles(c); }
fe_type_layout_all(&c->types);
for (u=0;u<c->build->count;++u) {
enter_unit(c,u);
c->unit_scope[u]=declare_unit_scope(c,&s);
}
for (u=0;u<c->build->count;++u) {
enter_unit(c,u);
s.scope=c->unit_scope[u];
s.globals=c->unit_scope[u];
check_unit_bodies(c,&s);
}
fe_type_layout_all(&c->types);
return c->diags->errors==0;
}
+756
View File
@@ -0,0 +1,756 @@
#include "checkpri.h"
void check_match(FeCheckerState *s, FeNode *n)
{
FeType *value;
FeNode *arm;
FeVariantType *variant;
int seen[256];
int wildcard=0;
FeFlowSlot base[64], merged[64], current[64];
unsigned flow_count;
int have_merged=0;
unsigned i;
for(i=0;i<256U;i++) seen[i]=0;
value=check_expr(s,n->a);
if(!value || value->kind!=FE_TYPE_ENUM) { err(s->c,n->loc,"match requires an enum value"); return; }
flow_count=flow_capture(s->scope,base,64);
for(arm=n->children;arm;arm=arm->next) {
FeScope *old=s->scope;
flow_restore(base,flow_count);
if(arm->text && strcmp(arm->text,"_")==0) wildcard=1;
else {
variant=fe_type_variant(value,arm->text);
if(!variant) { err(s->c,arm->loc,"unknown match variant"); continue; }
if(variant->tag<256U) {
if(seen[variant->tag]) err(s->c,arm->loc,"duplicate match variant");
seen[variant->tag]=1;
}
s->scope=scope_new(s,old);
if(variant->field_count==1 && arm->children) {
add_symbol(s,s->scope,arm->children->text,variant->fields[0].type,0,0,1,
local_cname(s->c,arm->children->text),arm->children);
} else if(variant->field_count>0) {
FeNode *b=arm->children;
for(i=0;i<variant->field_count && b;i++,b=b->next) {
FeFieldType *f=&variant->fields[i];
add_symbol(s,s->scope,b->text,f->type,0,0,1,
local_cname(s->c,b->text),b);
}
}
}
if(arm->a && arm->a->kind==FE_N_BLOCK) check_stmt(s,arm->a);
else if(arm->a) check_expr(s,arm->a);
s->scope=old;
flow_capture(s->scope,current,flow_count);
if(!have_merged) {
for(i=0;i<flow_count;++i) merged[i]=current[i];
have_merged=1;
} else {
for(i=0;i<flow_count;++i) {
merged[i].moved=fe_own_merge_move(merged[i].moved,current[i].moved);
merged[i].initialized=merged[i].initialized && current[i].initialized;
}
}
}
if(have_merged) flow_restore(merged,flow_count);
if(!wildcard) for(i=0;i<value->variant_count && i<256U;i++) if(!seen[i]) err(s->c,n->loc,"non-exhaustive match");
}
void check_for(FeCheckerState *s, FeNode *n)
{
FeType *start;
FeType *finish;
FeType *elem;
FeType *ref_type;
FeSym *iter_sym;
char *index_cname;
char *item_cname;
int iter_mut;
FeScope *old=s->scope;
if(!n->c) {
start=check_expr(s,n->a);
if (!fe_type_is_indexable(start)) {
err(s->c,n->loc,"for iterable must be an array, slice, or str");
return;
}
elem=start->elem;
iter_sym=0;
if (n->a && n->a->kind==FE_N_IDENT)
iter_sym=find_symbol(s->scope,n->a->text ? n->a->text : "");
else if (n->a && n->a->kind==FE_N_INDEX && n->a->a &&
n->a->a->kind==FE_N_IDENT)
iter_sym=find_symbol(s->scope,n->a->a->text ? n->a->a->text : "");
iter_mut=start->kind==FE_TYPE_SLICE ? start->ref_mut :
(iter_sym && iter_sym->mutable);
ref_type=fe_type_ref(&s->c->types,elem,iter_mut);
if (iter_mut) n->flags |= 4U;
s->scope=scope_new(s,old);
if (n->aux_text) {
index_cname=local_cname(s->c,n->text ? n->text : "index");
item_cname=local_cname(s->c,n->aux_text);
add_symbol(s,s->scope,n->text,fe_type_intern(&s->c->types,"usize"),0,0,1,
index_cname,n);
add_symbol(s,s->scope,n->aux_text,ref_type,0,iter_mut,1,
item_cname,0);
n->cname=index_cname;
n->aux_cname=item_cname;
} else {
item_cname=local_cname(s->c,n->text ? n->text : "item");
add_symbol(s,s->scope,n->text,ref_type,0,iter_mut,1,
item_cname,n);
n->cname=item_cname;
}
/* Walking a container borrows it for the length of the walk: the
item is a reference into it, so writing the container underneath
would move what that reference points at (SPEC 5 R6). */
if (iter_sym)
fe_own_access(s->c->diags,&iter_sym->own,
iter_mut ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
n->loc);
check_stmt(s,n->b);
if (iter_sym) {
if (iter_mut) fe_own_release_exclusive(&iter_sym->own);
else fe_own_release_shared(&iter_sym->own);
}
s->scope=old;
return;
}
start=check_expr(s,n->a);
finish=check_expr(s,n->c);
if(known(start)&&!fe_type_is_integer(start)) err(s->c,n->loc,"range start must be integer");
if(known(finish)&&!fe_type_is_integer(finish)) err(s->c,n->loc,"range end must be integer");
s->scope=scope_new(s,old);
index_cname=local_cname(s->c,n->text ? n->text : "index");
add_symbol(s,s->scope,n->text,fe_type_intern(&s->c->types,"usize"),0,0,1,
index_cname,n);
n->cname=index_cname;
check_stmt(s,n->b);
s->scope=old;
}
void check_type_cycle(FeCheck *c, FeType *t)
{
unsigned i;
FeType *next;
if (!t || t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR ||
t->kind == FE_TYPE_REF || t->kind == FE_TYPE_OWNED ||
t->kind == FE_TYPE_INT || t->kind == FE_TYPE_BOOL ||
t->kind == FE_TYPE_CHAR || t->kind == FE_TYPE_VOID ||
t->kind == FE_TYPE_UNKNOWN || t->kind == FE_TYPE_ERROR) return;
if (t->kind == FE_TYPE_ERROR_UNION) {
check_type_cycle(c,t->error_value);
return;
}
if (t->cycle_state == 1) {
if (c->ast->root) err(c, c->ast->root->loc, "by-value recursive type");
return;
}
if (t->cycle_state == 2) return;
t->cycle_state = 1;
if (t->kind == FE_TYPE_ARRAY) {
check_type_cycle(c,t->elem);
} else if (t->kind == FE_TYPE_STRUCT) {
int back=enter_declaring_unit(c,t->unit);
for (i=0;i<t->field_count;i++)
if (!t->fields[i].type && t->fields[i].ast_node)
t->fields[i].type=fe_type_from_ast(&c->types,t->fields[i].ast_node->a);
if (back>=0) enter_unit(c,(unsigned)back);
for (i=0;i<t->field_count;i++) check_type_cycle(c,t->fields[i].type);
} else if (t->kind == FE_TYPE_ENUM) {
int back=enter_declaring_unit(c,t->unit);
for (i=0;i<t->variant_count;i++) {
unsigned j;
for (j=0;j<t->variants[i].field_count;j++)
if (!t->variants[i].fields[j].type && t->variants[i].fields[j].ast_node)
t->variants[i].fields[j].type=fe_type_from_ast(&c->types,
t->variants[i].fields[j].ast_node->a);
}
if (back>=0) enter_unit(c,(unsigned)back);
for (i=0;i<t->variant_count;i++) {
unsigned j;
for (j=0;j<t->variants[i].field_count;j++) {
next=t->variants[i].fields[j].type;
check_type_cycle(c,next);
}
}
}
t->cycle_state=2;
}
void check_type_cycles(FeCheck *c)
{
FeType *t;
for (t=c->types.types;t;t=t->next) t->cycle_state=0;
for (t=c->types.types;t;t=t->next) check_type_cycle(c,t);
}
int own_ast_reference_type(FeNode *type)
{
if (!type || !type->text) return 0;
return strcmp(type->text,"&")==0 || strcmp(type->text,"&mut")==0 ||
(strcmp(type->text,"[")==0 && !type->a) || strcmp(type->text,"str")==0;
}
int own_ast_pointer_to_reference(FeNode *type)
{
return type && type->text && strcmp(type->text,"*")==0 &&
own_ast_reference_type(type->a);
}
void check_reference_storage(FeCheck *c, FeNode *decl)
{
FeNode *m;
if (!decl) return;
if (decl->kind==FE_N_STRUCT || decl->kind==FE_N_ENUM) {
for (m=decl->children;m;m=m->next)
if (m->kind==FE_N_FIELD &&
(own_ast_reference_type(m->a) || own_ast_pointer_to_reference(m->a)))
err(c,m->loc,"reference type is not allowed in aggregate storage");
}
if ((decl->kind==FE_N_GLOBAL || decl->kind==FE_N_CONST) && decl->a &&
own_ast_reference_type(decl->a) &&
!(decl->kind==FE_N_CONST && decl->a->text && strcmp(decl->a->text,"str")==0))
err(c,decl->loc,"reference type is not allowed in global storage");
if (decl->kind==FE_N_FN && decl->b && own_ast_pointer_to_reference(decl->b))
err(c,decl->b->loc,"reference type is not allowed as a pointer target");
if (decl->kind==FE_N_FN)
for (m=decl->a ? decl->a->children : 0;m;m=m->next)
if (own_ast_pointer_to_reference(m->a))
err(c,m->loc,"reference type is not allowed as a pointer target");
}
int own_return_from_allowed_root(FeCheckerState *s, FeNode *expr)
{
FeSym *root;
FeNode *p;
unsigned refs=0;
if (!expr) return 0;
root=own_root_symbol(s,expr);
if (!root) return 1; /* Static-producing builtins/methods are checked by
their declared R8 interface. */
if (own_is_global(s,root))
return root->decl && root->decl->kind==FE_N_GLOBAL &&
(root->decl->flags & 2U);
if (!root->decl || root->decl->kind!=FE_N_PARAM) return 0;
for (p=s->fn_node && s->fn_node->a ? s->fn_node->a->children : 0;
p;p=p->next) {
FeType *t=p->sem_type ? p->sem_type : node_type(s->c,p->a);
if (fe_own_is_reference_like(t)) ++refs;
}
if (s->fn_node && s->fn_node->text && refs &&
root->name && strcmp(root->name,"self")==0) return 1;
return refs==1;
}
void check_stmt_core(FeCheckerState *s, FeNode *n)
{
FeCheck *c = s->c;
FeScope *old;
FeType *a;
FeType *b;
FeSym *sym;
FeNode *x;
int initialized;
if (!n) return;
switch (n->kind) {
case FE_N_BLOCK:
old = s->scope;
s->scope = scope_new(s, old);
for (x = n->children; x; x = x->next) {
check_stmt(s,x);
own_release_after_stmt(s,s->scope,x,0);
}
own_release_after_stmt(s,s->scope,n,1);
s->scope = old;
break;
case FE_N_LET:
case FE_N_CONST:
a = n->a ? node_type(c, n->a) : unknown(c);
b = check_expr(s, n->b);
if (!n->a) a = b;
if (a->kind == FE_TYPE_VOID)
err(c, n->loc, "variable cannot have void type");
if (n->a && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN)
err(c, n->loc, "initializer type mismatch");
if (b->kind == FE_TYPE_VOID)
err(c, n->loc, "void expression cannot initialize a variable");
if (n->kind==FE_N_LET && a->kind==FE_TYPE_SLICE && a->ref_mut)
err(c,n->loc,"let cannot bind a mutable slice");
mark_moved(s,n->b,b);
sym=add_symbol(s, s->scope, n->text, a, 0, 0, 1,
local_cname(c, n->text ? n->text : "local"), n);
if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text &&
(strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) {
sym->borrow_root=own_root_symbol(s,n->b->a);
sym->borrow_field=own_projected_field(n->b->a,0);
sym->borrow_mut=strcmp(n->b->text,"&mut")==0;
sym->borrow_defer=s->defer_depth != 0 ||
own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text);
}
own_bind_derived_call(s,sym,n->b);
break;
case FE_N_VAR:
a = n->a ? node_type(c, n->a) : unknown(c);
if (!n->b && !n->a)
err(c, n->loc, "uninitialized var requires an explicit type");
b = n->b ? check_expr(s, n->b) : unknown(c);
if (!n->a && n->b) a = b;
if (a->kind == FE_TYPE_VOID)
err(c, n->loc, "variable cannot have void type");
if (n->b && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN)
err(c, n->loc, "initializer type mismatch");
if (b->kind == FE_TYPE_VOID)
err(c, n->loc, "void expression cannot initialize a variable");
mark_moved(s,n->b,b);
initialized = n->b != 0;
sym=add_symbol(s, s->scope, n->text, a, 0, 1, initialized,
local_cname(c, n->text ? n->text : "local"), n);
if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text &&
(strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) {
sym->borrow_root=own_root_symbol(s,n->b->a);
sym->borrow_field=own_projected_field(n->b->a,0);
sym->borrow_mut=strcmp(n->b->text,"&mut")==0;
sym->borrow_defer=s->defer_depth != 0 ||
own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text);
}
own_bind_derived_call(s,sym,n->b);
break;
case FE_N_ASSIGN:
b = check_expr(s, n->b);
a = check_lvalue(s, n->a, compound_operator(n->text));
if (!compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN)
err(c, n->loc, "assignment type mismatch");
mark_moved(s,n->b,b);
sym = n->a && n->a->kind == FE_N_IDENT ?
find_symbol(s->scope, n->a->text) : 0;
if (sym && sym->mutable) {
sym->initialized = 1;
fe_own_access(s->c->diags,&sym->own,FE_OWN_WRITE,n->a->loc);
sym->moved=sym->own.move;
if (n->b && n->b->kind==FE_N_UNARY && n->b->text &&
(strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0) &&
fe_own_is_reference_like(sym->type)) {
FeSym *root=own_root_symbol(s,n->b->a);
if (root && root->owner!=sym->owner)
err(c,n->b->loc,"reference would outlive its source scope");
else if (root) {
if (sym->borrow_root) {
if (sym->borrow_mut) fe_own_release_exclusive(&sym->borrow_root->own);
else fe_own_release_shared(&sym->borrow_root->own);
}
sym->borrow_root=root;
sym->borrow_mut=strcmp(n->b->text,"&mut")==0;
}
}
}
break;
case FE_N_EXPR_STMT:
/* The enclosing-error-result check lives on the try expression itself,
so a bare `try e;` needs nothing extra here. */
check_expr(s, n->a);
break;
case FE_N_DEFER:
++s->defer_depth;
check_stmt(s,n->a);
--s->defer_depth;
break;
case FE_N_IF: {
FeFlowSlot base[64], left[64], right[64];
FeOwnState *own_base, *own_left, *own_right;
FeFlowBorrow *borrow_base, *borrow_left, *borrow_right;
unsigned flow_count;
a = check_expr(s, n->a);
if (known(a) && a->kind != FE_TYPE_BOOL)
err(c, n->loc, "if condition must be bool");
flow_count=flow_capture(s->scope,base,64);
own_base=flow_own_new(s,flow_count);
own_left=flow_own_new(s,flow_count);
own_right=flow_own_new(s,flow_count);
borrow_base=flow_borrow_new(s,flow_count);
borrow_left=flow_borrow_new(s,flow_count);
borrow_right=flow_borrow_new(s,flow_count);
flow_own_capture(base,own_base,flow_count);
flow_borrow_capture(base,borrow_base,flow_count);
check_stmt(s, n->b);
flow_capture(s->scope,left,flow_count);
flow_own_capture(left,own_left,flow_count);
flow_borrow_capture(left,borrow_left,flow_count);
flow_restore(base,flow_count);
flow_own_restore(base,own_base,flow_count);
flow_borrow_restore(base,borrow_base,flow_count);
if (n->c) check_stmt(s, n->c);
if (n->c) {
flow_capture(s->scope,right,flow_count);
flow_own_capture(right,own_right,flow_count);
flow_borrow_capture(right,borrow_right,flow_count);
}
else {
unsigned i;
for (i=0;i<flow_count;++i) {
right[i]=base[i];
if (own_right && own_base) own_right[i]=own_base[i];
if (borrow_right && borrow_base) borrow_right[i]=borrow_base[i];
}
}
flow_merge(base,left,right,flow_count);
flow_own_merge(base,own_left,own_right,flow_count);
flow_borrow_merge(base,borrow_left,borrow_right,flow_count);
break;
}
case FE_N_WHILE: {
FeFlowSlot base[64], body[64], entry2[64];
FeOwnState *own_base, *own_body, *own_entry2;
FeFlowBorrow *borrow_base, *borrow_body, *borrow_entry2;
unsigned flow_count;
unsigned i;
a = check_expr(s, n->a);
if (known(a) && a->kind != FE_TYPE_BOOL)
err(c, n->loc, "while condition must be bool");
flow_count=flow_capture(s->scope,base,64);
own_base=flow_own_new(s,flow_count);
own_body=flow_own_new(s,flow_count);
own_entry2=flow_own_new(s,flow_count);
borrow_base=flow_borrow_new(s,flow_count);
borrow_body=flow_borrow_new(s,flow_count);
borrow_entry2=flow_borrow_new(s,flow_count);
flow_own_capture(base,own_base,flow_count);
flow_borrow_capture(base,borrow_base,flow_count);
if (s->loop_depth < 255U) ++s->loop_depth;
check_stmt(s, n->b);
if (s->loop_depth) --s->loop_depth;
flow_capture(s->scope,body,flow_count);
flow_own_capture(body,own_body,flow_count);
flow_borrow_capture(body,borrow_body,flow_count);
for (i=0;i<flow_count;++i) {
entry2[i]=base[i];
entry2[i].moved=fe_own_loop_entry(base[i].moved,body[i].moved);
if(!body[i].initialized) entry2[i].initialized=0;
entry2[i].own_move=fe_own_loop_entry(base[i].own_move,body[i].own_move);
if(!body[i].own_initialized) entry2[i].own_initialized=0;
if (own_entry2 && own_base && own_body)
fe_own_loop_merge_state(own_base[i],own_body[i],&own_entry2[i]);
if (borrow_entry2 && borrow_base && borrow_body)
borrow_entry2[i]=borrow_base[i].root ? borrow_base[i] : borrow_body[i];
}
flow_restore(entry2,flow_count);
flow_own_restore(entry2,own_entry2,flow_count);
flow_borrow_restore(entry2,borrow_entry2,flow_count);
if (s->loop_depth < 255U) ++s->loop_depth;
check_stmt(s,n->b);
if (s->loop_depth) --s->loop_depth;
flow_capture(s->scope,body,flow_count);
flow_own_capture(body,own_body,flow_count);
flow_borrow_capture(body,borrow_body,flow_count);
for(i=0;i<flow_count;++i) {
entry2[i].moved=fe_own_loop_exit(entry2[i].moved,body[i].moved);
if(!body[i].initialized) entry2[i].initialized=0;
entry2[i].own_move=fe_own_loop_exit(entry2[i].own_move,body[i].own_move);
if(!body[i].own_initialized) entry2[i].own_initialized=0;
if (own_entry2 && own_body)
fe_own_loop_merge_state(own_entry2[i],own_body[i],&own_entry2[i]);
if (borrow_entry2 && borrow_body && !borrow_entry2[i].root)
borrow_entry2[i]=borrow_body[i];
}
flow_restore(entry2,flow_count);
flow_own_restore(entry2,own_entry2,flow_count);
flow_borrow_restore(entry2,borrow_entry2,flow_count);
break;
}
case FE_N_FOR:
if (s->loop_depth < 255U) ++s->loop_depth;
check_for(s,n);
if (s->loop_depth) --s->loop_depth;
break;
case FE_N_MATCH:
check_match(s,n);
break;
case FE_N_BREAK:
case FE_N_CONTINUE:
if (!s->loop_depth) err(c,n->loc,"break or continue outside loop");
break;
case FE_N_RETURN:
/* A deferred block runs during scope cleanup, on the way out of a
function that has already decided what it returns. There is nothing
for a `return` in there to mean. */
if (s->defer_depth != 0)
err(c, n->loc, "cannot return from inside defer");
b = n->a ? check_expr(s, n->a) : fe_type_intern(&c->types, "void");
if (s->ret && fe_own_is_reference_like(s->ret) &&
!own_return_from_allowed_root(s,n->a))
err(c,n->loc,"reference return must be derived from a parameter or static");
mark_moved(s,n->a,b);
if (known(b) && b->kind == FE_TYPE_VOID && s->ret->kind != FE_TYPE_VOID)
err(c, n->loc, "void expression returned from value function");
else if (return_weakens(s->ret,b)) { }
else if (known(s->ret) && known(b) && !fe_type_equal(s->ret, b) &&
b->kind != FE_TYPE_UNKNOWN &&
!compatible(s->ret,b,n->a))
err(c, n->loc, "return type mismatch");
break;
case FE_N_ASM:
if (!s->unsafe_depth)
err(c,n->loc,"asm requires an unsafe block");
break;
case FE_N_UNSAFE:
check_stmt(s, n->a);
break;
default:
break;
}
}
void check_fn(FeCheck *c, FeNode *fn, FeScope *globals)
{
FeCheckerState s;
FeScope *old;
FeNode *x;
FeType *t;
s.c = c;
s.globals = globals;
s.scope = scope_new(&s, globals);
s.ret = fn->b ? node_type(c, fn->b) : fe_type_intern(&c->types, "void");
s.loop_depth=0;
s.defer_depth=0;
s.unsafe_depth=0;
s.fn_node=fn;
fe_own_liveness_init(&s.liveness,&c->arena);
fe_own_collect_last_uses(&s.liveness,fn);
fn->sem_type = s.ret;
for (x = fn->a ? fn->a->children : 0; x; x = x->next) {
t = node_type(c, x->a);
if (t->kind == FE_TYPE_VOID)
err(c, x->loc, "parameter cannot have void type");
add_symbol(&s, s.scope, x->text, t, 0, 1, 1,
local_cname(c, x->text ? x->text : "arg"), x);
}
old = s.scope;
if (fn->c) check_stmt(&s, fn->c);
s.scope = old;
}
void check_method(FeCheck *c, FeNode *fn, FeScope *globals,
FeType *owner)
{
FeCheckerState s;
FeNode *x;
FeType *t;
FeBindSave self_save;
/* `Self` names the type a method belongs to, wherever it appears -- in a
signature, and in `Self{ .. }`. Binding it as a type makes both work the
same way, and the same way a generic instance already worked. */
self_save.count=c->types.param_count;
{
unsigned i;
for(i=0;i<FE_TYPE_PARAM_MAX;++i) self_save.params[i]=c->types.params[i];
}
bind_self(c,owner);
s.c=c;
s.globals=globals;
s.scope=scope_new(&s,globals);
s.ret=fn->b ? method_type(c,fn->b,owner) : fe_type_intern(&c->types,"void");
s.loop_depth=0;
s.defer_depth=0;
s.unsafe_depth=0;
s.fn_node=fn;
fe_own_liveness_init(&s.liveness,&c->arena);
fe_own_collect_last_uses(&s.liveness,fn);
fn->sem_type=s.ret;
for(x=fn->a ? fn->a->children : 0; x; x=x->next) {
t=method_type(c,x->a,owner);
x->sem_type=t;
add_symbol(&s,s.scope,x->text,t,0,1,1,
local_cname(c,x->text ? x->text : "arg"),x);
}
if(fn->c) check_stmt(&s,fn->c);
pop_bindings(c,&self_save);
}
int m7_actual_compatible(FeType *want, FeType *got, FeNode *value)
{
if (fe_type_equal(want,got)) return 1;
return compatible(want,got,value);
}
/* The magnitude an integer literal spells, ignoring any sign. The same shape
lowering uses on the same text, so the two cannot disagree about what was
written. */
static unsigned long literal_magnitude(const char *s)
{
unsigned long v = 0;
unsigned long base = 10UL;
if (!s) return 0;
if (s[0]=='0' && (s[1]=='x' || s[1]=='X')) { base = 16UL; s += 2; }
else if (s[0]=='0' && (s[1]=='b' || s[1]=='B')) { base = 2UL; s += 2; }
else if (s[0]=='0' && (s[1]=='o' || s[1]=='O')) { base = 8UL; s += 2; }
for (; *s; ++s) {
unsigned long d;
if (*s=='_') continue;
if (*s>='0' && *s<='9') d = (unsigned long)(*s-'0');
else if (*s>='a' && *s<='f') d = (unsigned long)(*s-'a'+10);
else if (*s>='A' && *s<='F') d = (unsigned long)(*s-'A'+10);
else break;
if (d >= base) break;
v = v*base + d;
}
return v;
}
/* SPEC 4.1: an integer literal takes the type its context asks for, and a
value that does not fit that type is a mistake where it is written rather
than a truncation nobody sees. */
static int literal_fits(const FeType *want, const char *text, int negative)
{
unsigned long v;
unsigned long limit;
unsigned bits;
if (!want || want->kind != FE_TYPE_INT || !text) return 1;
bits = want->bits ? want->bits : 32U;
if (bits > 32U) bits = 32U;
v = literal_magnitude(text);
if (want->is_unsigned) {
if (negative) return v == 0UL;
if (bits >= 32U) return 1;
return v <= (1UL << bits) - 1UL;
}
limit = bits >= 32U ? 2147483647UL : (1UL << (bits - 1U)) - 1UL;
return v <= (negative ? limit + 1UL : limit);
}
/* Is this node a plain integer literal, rather than a character, a string, or
one of the word-shaped literals? */
static int plain_int_literal(const FeNode *n)
{
return n && n->kind==FE_N_LITERAL && n->text &&
n->text[0]!='\'' && n->text[0]!='"' &&
strcmp(n->text,"true") && strcmp(n->text,"false") &&
strcmp(n->text,"null") && strcmp(n->text,"undefined");
}
FeType *m7_check_expected(FeCheckerState *s, FeNode *value,
FeType *expected)
{
FeType *actual;
FeM7ContextKind context;
if (!value) return unknown(s->c);
/* `undefined` is not a value, it is the absence of one: it takes whatever
type was asked for, and says the storage starts out unset. Without this
there is no way to declare a buffer larger than you care to type out. */
if (value->kind==FE_N_LITERAL && value->text &&
!strcmp(value->text,"undefined") && expected) {
value->sem_type=expected;
return expected;
}
if (fe_m7_is_null(value)) {
if (!fe_m7_can_contextual_null(expected)) {
err(s->c,value->loc,"null requires a contextual optional type");
value->sem_type=unknown(s->c);
return value->sem_type;
}
value->sem_type=expected;
value->sem_context=expected;
return expected;
}
/* An integer literal is `i32` on its own; where an integer type is asked
for it is that type instead, and it has to fit in it. */
if (expected && expected->kind==FE_TYPE_INT) {
FeNode *lit = plain_int_literal(value) ? value :
(value->kind==FE_N_UNARY && value->text &&
!strcmp(value->text,"-") && plain_int_literal(value->a)
? value->a : 0);
if (lit) {
if (!literal_fits(expected, lit->text, lit!=value))
err(s->c,value->loc,"integer literal out of range for its type");
lit->sem_type=expected;
value->sem_type=expected;
return expected;
}
}
actual=check_expr(s,value);
if (!expected) return actual;
if (expected->kind==FE_TYPE_OPTIONAL && expected->elem &&
m7_actual_compatible(expected->elem,actual,value)) {
value->sem_context=expected;
return expected;
}
if (expected->kind==FE_TYPE_ERROR_UNION) {
context=fe_m7_error_context(&s->c->types,expected,actual);
if (context!=FE_M7_CONTEXT_NONE) {
value->sem_context=expected;
return expected;
}
}
return actual;
}
FeType *m7_member_field(FeCheckerState *s, FeNode *n, FeType *base)
{
FeFieldType *field;
FeType *owner;
if (!base) return unknown(s->c);
if (n->text && strcmp(n->text,".?")==0) {
if (base->kind!=FE_TYPE_OPTIONAL) {
err(s->c,n->loc,"optional projection '.?' requires an optional value");
return unknown(s->c);
}
n->sem_type=base->elem;
return n->sem_type;
}
if (base->kind==FE_TYPE_OPTIONAL) {
err(s->c,n->loc,"optional value must be projected with '.?' first");
return unknown(s->c);
}
if (base->kind==FE_TYPE_REF && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
n->sem_type=base->elem;
return n->sem_type;
}
if (base->kind==FE_TYPE_OWNED && n->b && n->b->text &&
strcmp(n->b->text,"^")==0) {
n->sem_type=base->elem;
return n->sem_type;
}
owner=base;
if ((base->kind==FE_TYPE_REF || base->kind==FE_TYPE_OWNED) &&
base->elem && base->elem->kind==FE_TYPE_STRUCT)
owner=base->elem;
if (owner && owner->kind==FE_TYPE_STRUCT && n->b && n->b->text) {
field=fe_type_field(owner,n->b->text);
if (!field) {
err(s->c,n->loc,"unknown struct field");
return unknown(s->c);
}
n->sem_type=field->type;
return field->type;
}
if (base->kind==FE_TYPE_ENUM && n->b && n->b->text) {
if (!fe_type_variant(base,n->b->text))
err(s->c,n->loc,"unknown enum variant");
n->sem_type=base;
return base;
}
if ((base->kind==FE_TYPE_SLICE || base->kind==FE_TYPE_STR) &&
n->b && n->b->text && strcmp(n->b->text,"n")==0) {
n->sem_type=fe_type_intern(&s->c->types,"usize");
return n->sem_type;
}
n->sem_type=unknown(s->c);
return n->sem_type;
}
int m7_place_is_projection(FeNode *n)
{
return n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX);
}
/* ------------------------------------------------------------------------- *
* Generics (SPEC 9)
*
* A generic declaration is checked once per distinct list of type arguments.
* Those arguments are bound as types for the length of that check, so a name
* that is a type parameter simply is its argument -- in the body, in field
* types and in the signature alike. An instance is identified by its declaring
* unit, its declaration and the spelling of its arguments, so asking twice
* asks for the same instance, and a chain of new ones is bounded.
* ------------------------------------------------------------------------- */
+95
View File
@@ -0,0 +1,95 @@
#include "diag.h"
#include <stdlib.h>
FILE *fe_diag_stream(void)
{
static FILE *stream=0;
if(!stream) stream=getenv("FE_DIAG_STDOUT") ? stdout : stderr;
return stream;
}
static unsigned long digits(unsigned long n)
{
unsigned long count=1;
while(n>=10UL){n/=10UL;++count;}
return count;
}
static void excerpt(const FeDiags *d, FeLoc loc)
{
const char *src;
unsigned long len;
unsigned long i=0;
unsigned long line=1;
unsigned long start;
unsigned long end;
unsigned long gutter;
unsigned long col;
char c;
if(!d || !d->source || !d->source_len || !loc.line || !loc.col) return;
src=d->source;
len=d->source_len;
while(i<len && line<loc.line){if(src[i++]=='\n')++line;}
if(line!=loc.line || i>len) return;
start=i;
end=start;
while(end<len && src[end]!='\n' && src[end]!='\r')++end;
gutter=digits(loc.line);
fputs(" ",fe_diag_stream());
fprintf(fe_diag_stream(),"%lu | ",loc.line);
if(end>start) fwrite(src+start,1,(size_t)(end-start),fe_diag_stream());
fputc('\n',fe_diag_stream());
fputs(" ",fe_diag_stream());
for(i=0;i<gutter;++i) fputc(' ',fe_diag_stream());
fputs(" | ",fe_diag_stream());
col=1;
i=start;
while(i<end && col<loc.col){
c=src[i++];
fputc(c=='\t' ? '\t' : ' ',fe_diag_stream());
++col;
}
fputs("^\n",fe_diag_stream());
}
void fe_diags_init(FeDiags *d, const char *source, unsigned long source_len)
{
d->errors=0;
d->warnings=0;
d->source=source;
d->source_len=source_len;
}
void fe_diags_source(FeDiags *d, const char *source, unsigned long source_len)
{
d->source=source;
d->source_len=source_len;
}
void fe_diag_error(FeDiags *d, FeLoc loc, const char *msg)
{
d->errors++;
fprintf(fe_diag_stream(), "%s:%lu:%lu: error: %s\n", loc.file ? loc.file : "<source>", loc.line, loc.col, msg);
excerpt(d,loc);
}
void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg)
{
d->errors++;
fprintf(fe_diag_stream(), "%s:%lu:%lu: error: ", loc.file ? loc.file : "<source>", loc.line, loc.col);
fprintf(fe_diag_stream(), msg, arg);
fputc('\n', fe_diag_stream());
excerpt(d,loc);
}
void fe_diag_note(FeLoc loc, const char *msg)
{
fprintf(fe_diag_stream(), "%s:%lu:%lu: note: %s\n", loc.file ? loc.file : "<source>", loc.line, loc.col, msg);
}
void fe_diag_note_src(FeDiags *d, FeLoc loc, const char *msg)
{
fe_diag_note(loc,msg);
excerpt(d,loc);
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef FE_DIAG_H
#define FE_DIAG_H
#include <stdio.h>
typedef struct FeLoc {
const char *file;
unsigned long line;
unsigned long col;
} FeLoc;
typedef struct FeDiags {
unsigned long errors;
unsigned long warnings;
const char *source;
unsigned long source_len;
} FeDiags;
/* Stream diagnostics are written to. Defaults to stderr; returns stdout when
FE_DIAG_STDOUT is set in the environment. DOS offers no way to redirect
handle 2 -- COMMAND.COM understands ">" and nothing else -- so under the test
runner every error message would otherwise be written straight to the screen
and lost. Interactive use is unaffected: both streams reach the console. */
FILE *fe_diag_stream(void);
void fe_diags_init(FeDiags *d, const char *source, unsigned long source_len);
/* Point the excerpt printer at a different file. A build spans several
units, and an excerpt drawn from the wrong buffer is worse than none. */
void fe_diags_source(FeDiags *d, const char *source, unsigned long source_len);
void fe_diag_error(FeDiags *d, FeLoc loc, const char *msg);
void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg);
void fe_diag_note(FeLoc loc, const char *msg);
void fe_diag_note_src(FeDiags *d, FeLoc loc, const char *msg);
#endif
+136
View File
@@ -0,0 +1,136 @@
#include "parser.h"
#include "check.h"
#include "resolve.h"
#include "lower.h"
#include "x86.h"
#include "report.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *read_file(const char *name, unsigned long *size)
{
FILE *f; long n; char *p;
f=fopen(name,"rb"); if(!f){fprintf(fe_diag_stream(),"fec: cannot open %s\n",name);return 0;}
if(fseek(f,0L,SEEK_END)!=0){fclose(f);return 0;} n=ftell(f); if(n<0){fclose(f);return 0;} rewind(f);
p=(char *)malloc((unsigned long)n+1); if(!p){fclose(f);return 0;}
if(n && fread(p,1,(size_t)n,f)!=(size_t)n){free(p);fclose(f);return 0;} fclose(f);p[n]='\0';*size=(unsigned long)n;return p;
}
static void usage(void)
{
puts("usage: fec [--dump-tokens|--dump-ast|--check|--dump-ir|--emit-asm] file.fe [-o out.asm] [--std=dir] [--no-checks]");
puts(" fec [--report-unsafe|--report-instances] file.fe [--std=dir]");
}
static void dump_tokens(const char *src, unsigned long n, const char *file,
FeDiags *d)
{
FeLexer lexer;
FeToken tok;
fe_lexer_init(&lexer,src,n,file,d);
do {
tok=fe_lexer_next(&lexer);
fprintf(stdout,"%lu:%lu\t%s\t",tok.loc.line,tok.loc.col,
fe_token_name(tok.kind));
if(tok.length) fwrite(tok.begin,1,(size_t)tok.length,stdout);
else fputc('-',stdout);
fputc('\n',stdout);
} while(tok.kind!=FE_TOK_EOF);
}
int main(int argc, char **argv)
{
int i,dump=0,dump_tok=0,check_only=0,no_checks=0,dump_ir=0,emit_asm=0;
int rep_unsafe=0,rep_inst=0;
const char *file=0;
const char *out_path=0;
const char *std_root=0;
unsigned long n;
char *src;
FeDiags d;
FeAst ast;
FeParser p;
FeCheck check;
unsigned pointer_bits=FE_PTR_BITS;
if(argc<2){usage();return 2;}
for(i=1;i<argc;i++) {
if(strcmp(argv[i],"--dump-ast")==0) dump=1;
else if(strcmp(argv[i],"--dump-tokens")==0) dump_tok=1;
else if(strcmp(argv[i],"--check")==0) check_only=1;
else if(strcmp(argv[i],"--dump-ir")==0) dump_ir=1;
else if(strcmp(argv[i],"--emit-asm")==0) emit_asm=1;
else if(strcmp(argv[i],"-o")==0 && i+1<argc) out_path=argv[++i];
else if(strncmp(argv[i],"--std=",6)==0) std_root=argv[i]+6;
else if(strcmp(argv[i],"--no-checks")==0) no_checks=1;
/* One target (SPEC 2), so --target= and --model= are gone: a flag
that is accepted and does nothing is worse than one that is not
accepted at all. */
else if(strcmp(argv[i],"--report-unsafe")==0) rep_unsafe=1;
else if(strcmp(argv[i],"--report-instances")==0) rep_inst=1;
else if(strcmp(argv[i],"--strip-error-names")==0) { }
else if(argv[i][0]!='-') file=argv[i];
else if(strcmp(argv[i],"--help")==0){usage();return 0;}
else {fprintf(fe_diag_stream(),"fec: unknown option %s\n",argv[i]);return 2;}
}
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)+(dump_ir?1:0)+(emit_asm?1:0)>1){
fprintf(fe_diag_stream(),"fec: choose only one output mode\n");
return 2;
}
if(!file){fprintf(fe_diag_stream(),"fec: no input file\n");return 2;}
src=read_file(file,&n);
if(!src)return 2;
fe_diags_init(&d,src,n);
if(dump_tok){
dump_tokens(src,n,file,&d);
free(src);
return d.errors?1:0;
}
fe_ast_init(&ast);
fe_parser_init(&p,&ast,src,n,file,&d);
ast.root=fe_parse_unit(&p);
if(dump){
fe_ast_dump(ast.root,0,stdout);
fe_ast_destroy(&ast);
free(src);
return d.errors?1:0;
}
fe_ast_destroy(&ast);
free(src);
src=0;
/* Load the whole unit graph rooted at this file: identity, imports,
cycles and bindings. The entry file is parsed a second time as part of
it, which costs one file read and keeps the graph the single owner of
every unit's AST. */
{
FeBuild build;
int ok=fe_build_load(&build,file,&d,std_root);
if(ok){
fe_check_init(&check,&build,&d,pointer_bits,no_checks);
if(!fe_check_program(&check)) ok=0;
/* Reports describe the program that was checked, so they come
after checking and instead of code generation. */
if(rep_unsafe) fe_report_unsafe(&build,stdout);
if(rep_inst) fe_report_instances(&check,stdout);
if(rep_unsafe||rep_inst) { dump_ir=0; emit_asm=0; }
if(ok && (dump_ir||emit_asm)){
FeIrModule ir;
fe_ir_module_init(&ir);
if(!fe_lower_program(&check,&ir)) ok=0;
else if(dump_ir) fe_ir_dump(&ir,stdout);
else {
FILE *o=out_path?fopen(out_path,"w"):stdout;
if(!o){fprintf(fe_diag_stream(),"fec: cannot write %s\n",out_path);ok=0;}
else { fe_x86_emit(&ir,o); if(out_path) fclose(o); }
}
fe_ir_module_destroy(&ir);
}
fe_check_destroy(&check);
}
fe_build_destroy(&build);
/* Semantic analysis is the last pass there is. A code generator
attaches here; until then --check and the default path agree. */
(void)check_only;
return (!ok||d.errors)?1:0;
}
}
+493
View File
@@ -0,0 +1,493 @@
#include "ir.h"
#include <string.h>
#include <stdio.h>
void fe_ir_module_init(FeIrModule *m)
{
fe_arena_init(&m->arena, 16384);
m->file_count = 0;
m->entry_main = 0;
m->funcs = 0;
m->last_func = 0;
m->globals = 0;
m->last_global = 0;
}
/* The index of this path in the module's file table, adding it if it is new.
Traps carry the index rather than the string so the generator emits each
name once. */
unsigned fe_ir_file(FeIrModule *m, const char *path)
{
unsigned i;
if (!path) path = "";
for (i = 0; i < m->file_count; ++i)
if (!strcmp(m->files[i], path)) return i;
if (m->file_count >= FE_IR_FILE_MAX) return 0;
m->files[m->file_count] = path;
return m->file_count++;
}
void fe_ir_module_destroy(FeIrModule *m)
{
fe_arena_destroy(&m->arena);
m->funcs = 0;
m->last_func = 0;
m->globals = 0;
m->last_global = 0;
}
static void *ir_alloc(FeIrModule *m, unsigned long size)
{
return fe_arena_alloc(&m->arena, (size_t)size);
}
FeIrFunc *fe_ir_func(FeIrModule *m, const char *name, FeIrType ret,
unsigned long ret_size)
{
FeIrFunc *f = (FeIrFunc *)ir_alloc(m, sizeof(FeIrFunc));
if (!f) return 0;
memset(f, 0, sizeof *f);
f->name = name;
f->ret = ret;
f->ret_size = ret_size;
/* An aggregate result is written through a hidden first parameter, so the
caller owns the storage and no size threshold has to be agreed on. */
f->returns_by_address = ret == FE_IR_MEM;
if (m->last_func) m->last_func->next = f;
else m->funcs = f;
m->last_func = f;
return f;
}
unsigned fe_ir_local(FeIrModule *m, FeIrFunc *f, FeIrType type,
unsigned long size, unsigned align, const char *name)
{
if (f->local_count == f->local_capacity) {
unsigned cap = f->local_capacity ? f->local_capacity * 2U : 8U;
FeIrLocal *grown = (FeIrLocal *)ir_alloc(m, cap * sizeof(FeIrLocal));
if (!grown) return 0;
if (f->locals)
memcpy(grown, f->locals, f->local_count * sizeof(FeIrLocal));
f->locals = grown;
f->local_capacity = cap;
}
f->locals[f->local_count].type = type;
f->locals[f->local_count].size = size;
f->locals[f->local_count].align = align ? align : 1U;
f->locals[f->local_count].name = name;
return f->local_count++;
}
unsigned fe_ir_temp(FeIrFunc *f)
{
return f->temp_count++;
}
FeIrBlock *fe_ir_block(FeIrModule *m, FeIrFunc *f)
{
FeIrBlock *b = (FeIrBlock *)ir_alloc(m, sizeof(FeIrBlock));
if (!b) return 0;
memset(b, 0, sizeof *b);
b->id = f->block_count++;
b->func = f;
/* Until something says otherwise a block falls off the end, which is only
correct for a void function; lowering always sets a real terminator. */
b->term = FE_IR_RET;
if (f->last) f->last->next = b;
else f->first = b;
f->last = b;
return b;
}
FeIrGlobal *fe_ir_global(FeIrModule *m, const char *name, FeIrType type,
unsigned long size, unsigned align,
const unsigned char *init)
{
FeIrGlobal *g;
for (g = m->globals; g; g = g->next)
if (!strcmp(g->name, name)) return g;
g = (FeIrGlobal *)ir_alloc(m, sizeof(FeIrGlobal));
if (!g) return 0;
memset(g, 0, sizeof *g);
g->name = name;
g->type = type;
g->size = size;
g->align = align ? align : 1U;
g->init = init;
if (m->last_global) m->last_global->next = g;
else m->globals = g;
m->last_global = g;
return g;
}
void fe_ir_global_ref(FeIrModule *m, FeIrGlobal *g, unsigned long at,
const char *symbol)
{
FeIrReloc *grown;
if (!g) return;
grown = (FeIrReloc *)ir_alloc(m, (g->reloc_count + 1) * sizeof(FeIrReloc));
if (!grown) return;
if (g->relocs) memcpy(grown, g->relocs, g->reloc_count * sizeof(FeIrReloc));
grown[g->reloc_count].at = at;
grown[g->reloc_count].symbol = symbol;
g->relocs = grown;
++g->reloc_count;
}
const char *fe_ir_string(FeIrModule *m, const char *bytes, unsigned long length)
{
FeIrGlobal *g;
unsigned char *copy;
char *name;
unsigned serial = 0;
/* The same text twice is the same storage: string literals are read-only,
so sharing them is free. */
for (g = m->globals; g; g = g->next) {
if (g->init && g->size == length &&
!memcmp(g->init, bytes, (size_t)length)) return g->name;
++serial;
}
copy = (unsigned char *)ir_alloc(m, length ? length : 1UL);
if (!copy) return 0;
if (length) memcpy(copy, bytes, (size_t)length);
name = (char *)ir_alloc(m, 32);
if (!name) return 0;
sprintf(name, "FE_STR_%u", serial);
g = fe_ir_global(m, name, FE_IR_MEM, length, 1, copy);
return g ? g->name : 0;
}
FeIrPlace fe_ir_at_local(unsigned index, long offset)
{
FeIrPlace p;
p.base = FE_PLACE_LOCAL; p.index = index; p.name = 0; p.offset = offset;
return p;
}
FeIrPlace fe_ir_at_global(const char *name, long offset)
{
FeIrPlace p;
p.base = FE_PLACE_GLOBAL; p.index = 0; p.name = name; p.offset = offset;
return p;
}
FeIrPlace fe_ir_at_temp(unsigned temp, long offset)
{
FeIrPlace p;
p.base = FE_PLACE_TEMP; p.index = temp; p.name = 0; p.offset = offset;
return p;
}
static FeIrValue *emit(FeIrModule *m, FeIrBlock *b, FeIrOp op, FeIrType t)
{
FeIrValue *v = (FeIrValue *)ir_alloc(m, sizeof(FeIrValue));
if (!v) return 0;
memset(v, 0, sizeof *v);
v->op = op;
v->type = t;
if (b->last) b->last->next = v;
else b->first = v;
b->last = v;
return v;
}
/* A result needs a fresh temporary, and the counter lives on the function, so
a block carries the function it is being built in. */
static unsigned result(FeIrModule *m, FeIrBlock *b, FeIrValue *v)
{
(void)m;
v->has_dest = 1;
v->dest = fe_ir_temp(b->func);
return v->dest;
}
unsigned fe_ir_const(FeIrModule *m, FeIrBlock *b, FeIrType t, long value)
{
FeIrValue *v = emit(m, b, FE_IR_CONST, t);
if (!v) return 0;
v->imm = value;
return result(m, b, v);
}
unsigned fe_ir_load(FeIrModule *m, FeIrBlock *b, FeIrType t, FeIrPlace p)
{
FeIrValue *v = emit(m, b, FE_IR_LOAD, t);
if (!v) return 0;
v->place = p;
return result(m, b, v);
}
void fe_ir_store(FeIrModule *m, FeIrBlock *b, FeIrPlace p, unsigned value,
FeIrType t)
{
FeIrValue *v = emit(m, b, FE_IR_STORE, t);
if (!v) return;
v->place = p;
v->a = value;
}
unsigned fe_ir_addr(FeIrModule *m, FeIrBlock *b, FeIrPlace p)
{
FeIrValue *v = emit(m, b, FE_IR_ADDR, FE_IR_PTR);
if (!v) return 0;
v->place = p;
return result(m, b, v);
}
unsigned fe_ir_binary(FeIrModule *m, FeIrBlock *b, FeIrOp op, FeIrType t,
unsigned a, unsigned c, int is_unsigned)
{
FeIrValue *v;
int is_cmp = op >= FE_IR_EQ && op <= FE_IR_GE;
v = emit(m, b, op, is_cmp ? FE_IR_I8 : t);
if (!v) return 0;
v->a = a;
v->b = c;
v->is_unsigned = is_unsigned;
/* A comparison reports i8 but reads its operands at `t`, so the width has
to survive somewhere the backend can see it. */
if (is_cmp) v->imm = (long)t;
return result(m, b, v);
}
unsigned fe_ir_cast(FeIrModule *m, FeIrBlock *b, FeIrType from, FeIrType to,
unsigned a, int is_unsigned)
{
FeIrValue *v = emit(m, b, FE_IR_CAST, to);
if (!v) return 0;
v->a = a;
v->imm = (long)from;
v->is_unsigned = is_unsigned;
return result(m, b, v);
}
unsigned fe_ir_call(FeIrModule *m, FeIrBlock *b, FeIrType ret,
const char *callee, unsigned *args, unsigned count)
{
FeIrValue *v = emit(m, b, FE_IR_CALL, ret);
unsigned i;
if (!v) return 0;
v->callee = callee;
v->arg_count = count;
if (count) {
v->args = (unsigned *)ir_alloc(m, count * sizeof(unsigned));
if (v->args) for (i = 0; i < count; ++i) v->args[i] = args[i];
else v->arg_count = 0;
}
if (ret == FE_IR_VOID) return 0;
return result(m, b, v);
}
void fe_ir_copy(FeIrModule *m, FeIrBlock *b, FeIrPlace dst, FeIrPlace src,
unsigned long size)
{
FeIrValue *v = emit(m, b, FE_IR_COPY, FE_IR_VOID);
if (!v) return;
v->place = dst;
v->place2 = src;
v->imm = (long)size;
}
void fe_ir_jmp(FeIrBlock *b, unsigned target)
{
if (b->terminated) return;
b->terminated = 1;
b->term = FE_IR_JMP;
b->target = target;
}
void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f)
{
if (b->terminated) return;
b->terminated = 1;
b->term = FE_IR_BR;
b->cond = cond;
b->target = t;
b->target_else = f;
}
void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value)
{
if (b->terminated) return;
b->terminated = 1;
b->term = FE_IR_RET;
b->ret_value = value;
b->has_ret_value = has_value;
}
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line,
unsigned file)
{
if (b->terminated) return;
b->terminated = 1;
b->term = FE_IR_TRAP;
b->trap = reason;
b->trap_line = line;
b->trap_file = file;
}
const char *fe_ir_type_name(FeIrType t)
{
switch (t) {
case FE_IR_VOID: return "void";
case FE_IR_I8: return "i8";
case FE_IR_I16: return "i16";
case FE_IR_I32: return "i32";
case FE_IR_PTR: return "ptr";
case FE_IR_MEM: return "mem";
}
return "?";
}
const char *fe_ir_op_name(FeIrOp op)
{
switch (op) {
case FE_IR_CONST: return "const";
case FE_IR_LOAD: return "load";
case FE_IR_STORE: return "store";
case FE_IR_ADDR: return "addr";
case FE_IR_ADD: return "add";
case FE_IR_SUB: return "sub";
case FE_IR_MUL: return "mul";
case FE_IR_DIV: return "div";
case FE_IR_MOD: return "mod";
case FE_IR_AND: return "and";
case FE_IR_OR: return "or";
case FE_IR_XOR: return "xor";
case FE_IR_SHL: return "shl";
case FE_IR_SHR: return "shr";
case FE_IR_EQ: return "eq";
case FE_IR_NE: return "ne";
case FE_IR_LT: return "lt";
case FE_IR_LE: return "le";
case FE_IR_GT: return "gt";
case FE_IR_GE: return "ge";
case FE_IR_CAST: return "cast";
case FE_IR_CALL: return "call";
case FE_IR_COPY: return "copy";
}
return "?";
}
static const char *trap_name(FeIrTrap t)
{
switch (t) {
case FE_TRAP_BOUNDS: return "bounds";
case FE_TRAP_OVERFLOW: return "overflow";
case FE_TRAP_DIVIDE: return "divide";
case FE_TRAP_UNREACHABLE: return "unreachable";
case FE_TRAP_EXPLICIT: return "trap";
}
return "?";
}
static void dump_place(const FeIrPlace *p, FILE *out)
{
switch (p->base) {
case FE_PLACE_LOCAL: fprintf(out, "$%u", p->index); break;
case FE_PLACE_GLOBAL: fprintf(out, "@%s", p->name ? p->name : "?"); break;
case FE_PLACE_TEMP: fprintf(out, "%%%u", p->index); break;
}
if (p->offset) fprintf(out, " + %ld", p->offset);
}
static void dump_value(const FeIrValue *v, FILE *out)
{
unsigned i;
fputs(" ", out);
if (v->has_dest) fprintf(out, "%%%u = ", v->dest);
switch (v->op) {
case FE_IR_CONST:
fprintf(out, "const %s %ld", fe_ir_type_name(v->type), v->imm);
break;
case FE_IR_LOAD:
fprintf(out, "load %s ", fe_ir_type_name(v->type));
dump_place(&v->place, out);
break;
case FE_IR_STORE:
fputs("store ", out);
dump_place(&v->place, out);
fprintf(out, ", %%%u", v->a);
break;
case FE_IR_ADDR:
fputs("addr ", out);
dump_place(&v->place, out);
break;
case FE_IR_CAST:
fprintf(out, "cast %s %s %%%u",
fe_ir_type_name((FeIrType)v->imm),
fe_ir_type_name(v->type), v->a);
break;
case FE_IR_CALL:
fprintf(out, "call @%s(", v->callee ? v->callee : "?");
for (i = 0; i < v->arg_count; ++i)
fprintf(out, "%s%%%u", i ? ", " : "", v->args[i]);
fputc(')', out);
break;
case FE_IR_COPY:
fputs("copy ", out);
dump_place(&v->place, out);
fputs(", ", out);
dump_place(&v->place2, out);
fprintf(out, ", %ld", v->imm);
break;
default:
fprintf(out, "%s %s %%%u, %%%u", fe_ir_op_name(v->op),
fe_ir_type_name(v->op >= FE_IR_EQ && v->op <= FE_IR_GE ?
(FeIrType)v->imm : v->type), v->a, v->b);
if (v->is_unsigned) fputs(" u", out);
break;
}
fputc('\n', out);
}
void fe_ir_dump(const FeIrModule *m, FILE *out)
{
const FeIrFunc *f;
const FeIrBlock *b;
const FeIrValue *v;
const FeIrGlobal *g;
unsigned i;
for (i = 0; i < m->file_count; ++i)
fprintf(out, "; file %u %s\n", i, m->files[i]);
for (g = m->globals; g; g = g->next)
fprintf(out, "global @%s : %s %lu\n", g->name,
fe_ir_type_name(g->type), g->size);
for (f = m->funcs; f; f = f->next) {
if (f->is_extern) {
fprintf(out, "extern fn @%s -> %s\n", f->name,
fe_ir_type_name(f->ret));
continue;
}
fprintf(out, "fn @%s -> %s%s {\n", f->name, fe_ir_type_name(f->ret),
f->returns_by_address ? " (by address)" : "");
for (i = 0; i < f->local_count; ++i) {
fprintf(out, " $%u: %s", i, fe_ir_type_name(f->locals[i].type));
if (f->locals[i].type == FE_IR_MEM)
fprintf(out, "<%lu>", f->locals[i].size);
if (i < f->param_count) fputs(" ; parameter", out);
if (f->locals[i].name) fprintf(out, " ; %s", f->locals[i].name);
fputc('\n', out);
}
for (b = f->first; b; b = b->next) {
fprintf(out, " b%u:\n", b->id);
for (v = b->first; v; v = v->next) dump_value(v, out);
switch (b->term) {
case FE_IR_JMP:
fprintf(out, " jmp b%u\n", b->target); break;
case FE_IR_BR:
fprintf(out, " br %%%u, b%u, b%u\n", b->cond, b->target,
b->target_else); break;
case FE_IR_RET:
if (b->has_ret_value) fprintf(out, " ret %%%u\n", b->ret_value);
else fputs(" ret\n", out);
break;
case FE_IR_TRAP:
fprintf(out, " trap %s %lu\n", trap_name(b->trap),
b->trap_line);
break;
}
}
fputs("}\n", out);
}
}
+216
View File
@@ -0,0 +1,216 @@
#ifndef FE_IR_H
#define FE_IR_H
#include "arena.h"
#include <stdio.h>
/* How many source files one build can trap from. */
#define FE_IR_FILE_MAX 64
/* The intermediate representation. `IR.md` is the description; this is the
shape it takes in memory.
Ferro types do not survive into here. A struct, a slice, an optional and an
error union are all `mem<N>`, and a field is a byte offset that lowering
worked out. The machine types are what a register can hold plus a size. */
typedef enum FeIrType {
FE_IR_VOID,
FE_IR_I8, FE_IR_I16, FE_IR_I32,
FE_IR_PTR,
FE_IR_MEM /* size lives on the value or slot */
} FeIrType;
typedef enum FeIrOp {
FE_IR_CONST, FE_IR_LOAD, FE_IR_STORE, FE_IR_ADDR,
FE_IR_ADD, FE_IR_SUB, FE_IR_MUL, FE_IR_DIV, FE_IR_MOD,
FE_IR_AND, FE_IR_OR, FE_IR_XOR, FE_IR_SHL, FE_IR_SHR,
FE_IR_EQ, FE_IR_NE, FE_IR_LT, FE_IR_LE, FE_IR_GT, FE_IR_GE,
FE_IR_CAST, FE_IR_CALL, FE_IR_COPY
} FeIrOp;
typedef enum FeIrTerm {
FE_IR_JMP, FE_IR_BR, FE_IR_RET, FE_IR_TRAP
} FeIrTerm;
/* Why a program stopped. Kept small and stable: it is a number in the
executable, and the runtime turns it back into words. */
typedef enum FeIrTrap {
FE_TRAP_BOUNDS = 0,
FE_TRAP_OVERFLOW = 1,
FE_TRAP_DIVIDE = 2,
FE_TRAP_UNREACHABLE = 3,
FE_TRAP_EXPLICIT = 4
} FeIrTrap;
/* Where an instruction reads or writes. A place is a base plus a constant
offset; anything computed goes through a pointer temporary instead. */
typedef enum FeIrBase {
FE_PLACE_LOCAL, /* $n */
FE_PLACE_GLOBAL, /* @name */
FE_PLACE_TEMP /* %p */
} FeIrBase;
typedef struct FeIrPlace {
FeIrBase base;
unsigned index; /* local or temp number */
const char *name; /* global name */
long offset;
} FeIrPlace;
typedef struct FeIrValue {
FeIrOp op;
FeIrType type;
unsigned dest; /* %dest, or 0 when the op has no result */
int has_dest;
/* operands, by role -- only the ones the op uses are set */
unsigned a, b; /* temporaries */
long imm; /* const, cast width, copy size */
FeIrPlace place; /* load / store / addr / copy destination */
FeIrPlace place2; /* copy source */
const char *callee;
unsigned *args;
unsigned arg_count;
int is_unsigned; /* picks the signed or unsigned instruction */
unsigned long line; /* for diagnostics that survive into the backend */
struct FeIrValue *next;
} FeIrValue;
typedef struct FeIrFunc FeIrFunc;
typedef struct FeIrBlock {
unsigned id;
FeIrFunc *func; /* the function this block is being built in */
FeIrValue *first;
FeIrValue *last;
FeIrTerm term;
unsigned cond; /* br */
unsigned target; /* jmp, br true */
unsigned target_else; /* br false */
unsigned ret_value; /* ret */
int has_ret_value;
FeIrTrap trap;
unsigned long trap_line;
unsigned trap_file; /* index into the module's file table */
/* Set once a terminator is chosen. Lowering asks before appending a
jump, so a `return` inside a branch is not overwritten by the jump
to the join block. */
int terminated;
struct FeIrBlock *next;
} FeIrBlock;
typedef struct FeIrLocal {
FeIrType type;
unsigned long size; /* for FE_IR_MEM */
unsigned align;
const char *name; /* the Ferro name, for reading the dump */
} FeIrLocal;
struct FeIrFunc {
const char *name; /* unit.name */
FeIrType ret;
unsigned long ret_size; /* when ret is FE_IR_MEM */
/* A function returning mem<N> takes the address to write as a hidden
first parameter, so the caller owns the storage. */
int returns_by_address;
FeIrLocal *locals;
unsigned local_count;
unsigned local_capacity;
unsigned param_count; /* the first `param_count` locals are parameters */
unsigned temp_count;
FeIrBlock *first;
FeIrBlock *last;
unsigned block_count;
int is_extern;
struct FeIrFunc *next;
};
/* A place inside a global's bytes that holds the address of something else.
The value is not known until the linker places it, so the bytes carry a hole
and this says what fills it. */
typedef struct FeIrReloc {
unsigned long at;
const char *symbol;
} FeIrReloc;
typedef struct FeIrGlobal {
const char *name;
FeIrType type;
unsigned long size;
unsigned align;
const unsigned char *init; /* size bytes, or null for zero */
FeIrReloc *relocs;
unsigned reloc_count;
struct FeIrGlobal *next;
} FeIrGlobal;
typedef struct FeIrModule {
FeArena arena;
/* Every unit in the build lands in one module, so a trap has to say which
file it came from rather than share one name with the whole program. */
const char *files[FE_IR_FILE_MAX];
unsigned file_count;
/* The entry unit's `main`, if it has one. The runtime's start stub
calls a fixed name, so the generator emits a jump to this one. */
const char *entry_main;
FeIrFunc *funcs;
FeIrFunc *last_func;
FeIrGlobal *globals;
FeIrGlobal *last_global;
} FeIrModule;
unsigned fe_ir_file(FeIrModule *m, const char *path);
void fe_ir_module_init(FeIrModule *m);
void fe_ir_module_destroy(FeIrModule *m);
FeIrFunc *fe_ir_func(FeIrModule *m, const char *name, FeIrType ret,
unsigned long ret_size);
unsigned fe_ir_local(FeIrModule *m, FeIrFunc *f, FeIrType type,
unsigned long size, unsigned align, const char *name);
unsigned fe_ir_temp(FeIrFunc *f);
FeIrBlock *fe_ir_block(FeIrModule *m, FeIrFunc *f);
/* Static storage. `init` is `size` bytes to place there, or null for zero. */
FeIrGlobal *fe_ir_global(FeIrModule *m, const char *name, FeIrType type,
unsigned long size, unsigned align,
const unsigned char *init);
/* Say that `at` bytes into `g` there is the address of `symbol`. */
void fe_ir_global_ref(FeIrModule *m, FeIrGlobal *g, unsigned long at,
const char *symbol);
/* A string literal's bytes, interned so the same text is stored once. */
const char *fe_ir_string(FeIrModule *m, const char *bytes,
unsigned long length);
/* Places */
FeIrPlace fe_ir_at_local(unsigned index, long offset);
FeIrPlace fe_ir_at_global(const char *name, long offset);
FeIrPlace fe_ir_at_temp(unsigned temp, long offset);
/* Instructions. Each returns the destination temporary where there is one. */
unsigned fe_ir_const(FeIrModule *m, FeIrBlock *b, FeIrType t, long v);
unsigned fe_ir_load(FeIrModule *m, FeIrBlock *b, FeIrType t, FeIrPlace p);
/* `t` is how wide the write is. Without it a one-byte value would be stored
four bytes wide and take its neighbours with it. */
void fe_ir_store(FeIrModule *m, FeIrBlock *b, FeIrPlace p, unsigned v,
FeIrType t);
unsigned fe_ir_addr(FeIrModule *m, FeIrBlock *b, FeIrPlace p);
unsigned fe_ir_binary(FeIrModule *m, FeIrBlock *b, FeIrOp op, FeIrType t,
unsigned a, unsigned c, int is_unsigned);
unsigned fe_ir_cast(FeIrModule *m, FeIrBlock *b, FeIrType from, FeIrType to,
unsigned a, int is_unsigned);
unsigned fe_ir_call(FeIrModule *m, FeIrBlock *b, FeIrType ret,
const char *callee, unsigned *args, unsigned count);
void fe_ir_copy(FeIrModule *m, FeIrBlock *b, FeIrPlace dst, FeIrPlace src,
unsigned long size);
/* Terminators */
void fe_ir_jmp(FeIrBlock *b, unsigned target);
void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f);
void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value);
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line,
unsigned file);
void fe_ir_dump(const FeIrModule *m, FILE *out);
const char *fe_ir_type_name(FeIrType t);
const char *fe_ir_op_name(FeIrOp op);
#endif
+172
View File
@@ -0,0 +1,172 @@
#include "lexer.h"
#include <ctype.h>
#include <string.h>
typedef struct FeKw { const char *s; FeTokKind k; } FeKw;
static const FeKw keywords[] = {
{"unit",FE_TOK_UNIT},{"import",FE_TOK_IMPORT},{"pub",FE_TOK_PUB},{"fn",FE_TOK_FN},
{"struct",FE_TOK_STRUCT},{"enum",FE_TOK_ENUM},{"error",FE_TOK_ERROR_KW},{"const",FE_TOK_CONST},
{"static",FE_TOK_STATIC},{"var",FE_TOK_VAR},{"let",FE_TOK_LET},{"mut",FE_TOK_MUT},
{"if",FE_TOK_IF},{"else",FE_TOK_ELSE},{"while",FE_TOK_WHILE},{"for",FE_TOK_FOR},{"in",FE_TOK_IN},
{"match",FE_TOK_MATCH},{"return",FE_TOK_RETURN},{"break",FE_TOK_BREAK},{"continue",FE_TOK_CONTINUE},
{"defer",FE_TOK_DEFER},{"unsafe",FE_TOK_UNSAFE},{"comptime",FE_TOK_COMPTIME},{"asm",FE_TOK_ASM},
{"try",FE_TOK_TRY},{"catch",FE_TOK_CATCH},{"as",FE_TOK_AS},{"extern",FE_TOK_EXTERN},
{"interrupt",FE_TOK_INTERRUPT},{"interrupt_safe",FE_TOK_INTERRUPT_SAFE},
{"true",FE_TOK_TRUE},{"false",FE_TOK_FALSE},{"null",FE_TOK_NULL},{"undefined",FE_TOK_UNDEFINED},
{"shared",FE_TOK_SHARED},{"atomic",FE_TOK_ATOMIC},{"critical",FE_TOK_CRITICAL},
{"self",FE_TOK_SELF},{"Self",FE_TOK_SELFTYPE},{"type",FE_TOK_TYPE},
{"packed",FE_TOK_PACKED},{"orelse",FE_TOK_ORELSE},{"and",FE_TOK_AND_KW},{"or",FE_TOK_OR_KW},{"not",FE_TOK_NOT},
{0,FE_TOK_UNKNOWN}
};
static int at(FeLexer *l, unsigned long n, char c) { return l->pos + n < l->length && l->src[l->pos+n] == c; }
static FeLoc here(FeLexer *l, unsigned long line, unsigned long col)
{ FeLoc x; x.file=l->file; x.line=line; x.col=col; return x; }
static char cur(FeLexer *l) { return l->pos < l->length ? l->src[l->pos] : '\0'; }
static void advance(FeLexer *l)
{
if (l->pos >= l->length) return;
if (l->src[l->pos] == '\n') { l->line++; l->col = 1; }
else l->col++;
l->pos++;
}
static void skip_space(FeLexer *l)
{
for (;;) {
while (isspace((unsigned char)cur(l))) advance(l);
if (at(l,0,'/') && at(l,1,'/')) {
while (cur(l) && cur(l) != '\n') advance(l);
continue;
}
if (at(l,0,'/') && at(l,1,'*')) {
unsigned long depth = 0;
advance(l); advance(l); depth = 1;
while (depth && cur(l)) {
if (at(l,0,'/') && at(l,1,'*')) { advance(l); advance(l); depth++; }
else if (at(l,0,'*') && at(l,1,'/')) { advance(l); advance(l); depth--; }
else advance(l);
}
if (depth) fe_diag_error(l->diags, here(l,l->line,l->col), "unterminated block comment");
continue;
}
break;
}
}
void fe_lexer_init(FeLexer *l, const char *src, unsigned long length, const char *file, FeDiags *d)
{
l->src=src; l->length=length; l->pos=0; l->line=1; l->col=1; l->file=file; l->diags=d;
}
static FeTokKind keyword(const char *s, unsigned long n)
{
unsigned long i;
for (i=0; keywords[i].s; i++) {
if (strlen(keywords[i].s)==n && memcmp(keywords[i].s,s,n)==0) return keywords[i].k;
}
return FE_TOK_IDENT;
}
static FeToken tok(FeLexer *l, FeTokKind k, unsigned long start, unsigned long line, unsigned long col)
{
FeToken t; t.kind=k; t.begin=l->src+start; t.length=l->pos-start; t.loc.file=l->file; t.loc.line=line; t.loc.col=col; return t;
}
static int digit_for_base(char c, int base)
{
int d;
if (c >= '0' && c <= '9') d=c-'0';
else if (c >= 'a' && c <= 'f') d=c-'a'+10;
else if (c >= 'A' && c <= 'F') d=c-'A'+10;
else return 0;
return d < base;
}
FeToken fe_lexer_next(FeLexer *l)
{
unsigned long start, line, col;
char c;
skip_space(l);
start=l->pos; line=l->line; col=l->col; c=cur(l);
if (!c) return tok(l,FE_TOK_EOF,start,line,col);
if (isalpha((unsigned char)c) || c=='_') {
advance(l);
while (isalnum((unsigned char)cur(l)) || cur(l)=='_') advance(l);
return tok(l,keyword(l->src+start,l->pos-start),start,line,col);
}
if (isdigit((unsigned char)c)) {
int base=10, had_digit=0;
if (c=='0' && (at(l,1,'x') || at(l,1,'X'))) { advance(l); advance(l); base=16; }
else if (c=='0' && (at(l,1,'b') || at(l,1,'B'))) { advance(l); advance(l); base=2; }
else if (c=='0' && (at(l,1,'o') || at(l,1,'O'))) { advance(l); advance(l); base=8; }
while (cur(l)=='_' || digit_for_base(cur(l),base)) { if(cur(l)!='_') had_digit=1; advance(l); }
if (!had_digit) fe_diag_error(l->diags,here(l,line,col),"integer literal has no digits");
if (isalnum((unsigned char)cur(l))) {
fe_diag_error(l->diags,here(l,line,col),"invalid digit in integer literal");
while (isalnum((unsigned char)cur(l)) || cur(l)=='_') advance(l);
}
return tok(l,FE_TOK_INT,start,line,col);
}
if (c=='\'' || c=='"') {
char quote=c; int bad=0, units=0; advance(l);
while (cur(l) && cur(l)!=quote) {
if (cur(l)=='\n' || cur(l)=='\r') { bad=1; break; }
units++;
if (cur(l)=='\\') {
advance(l);
if (!cur(l)) { bad=1; break; }
if (cur(l)=='x') { int i; advance(l); for(i=0;i<2;i++) { if(!digit_for_base(cur(l),16)) bad=1; else advance(l); } }
else if (cur(l)=='u') { int i; advance(l); for(i=0;i<4;i++) { if(!digit_for_base(cur(l),16)) bad=1; else advance(l); } }
else if (strchr("nrt\\'\"0",cur(l))) advance(l);
else { bad=1; advance(l); }
} else advance(l);
}
if (cur(l)==quote) advance(l); else bad=1;
if (quote=='\'' && units != 1) bad=1;
if (bad) fe_diag_error(l->diags,here(l,line,col),quote=='\''?"invalid character literal":"unterminated or invalid string literal");
return tok(l,quote=='\''?FE_TOK_CHAR:FE_TOK_STRING,start,line,col);
}
advance(l);
switch(c) {
case '(': return tok(l,FE_TOK_LPAREN,start,line,col); case ')': return tok(l,FE_TOK_RPAREN,start,line,col);
case '{': return tok(l,FE_TOK_LBRACE,start,line,col); case '}': return tok(l,FE_TOK_RBRACE,start,line,col);
case '[': return tok(l,FE_TOK_LBRACKET,start,line,col); case ']': return tok(l,FE_TOK_RBRACKET,start,line,col);
case ',': return tok(l,FE_TOK_COMMA,start,line,col); case ';': return tok(l,FE_TOK_SEMI,start,line,col);
case ':': return tok(l,FE_TOK_COLON,start,line,col); case '@': return tok(l,FE_TOK_AT,start,line,col);
case '?': return tok(l,FE_TOK_QUESTION,start,line,col);
case '.': if (cur(l)=='.') { advance(l); return tok(l,FE_TOK_DOTDOT,start,line,col); } return tok(l,FE_TOK_DOT,start,line,col);
case '+': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_PLUS_EQ,start,line,col);} if(cur(l)=='%'){advance(l);return tok(l,FE_TOK_PLUS_WRAP,start,line,col);} return tok(l,FE_TOK_PLUS,start,line,col);
case '-': if(cur(l)=='>'){advance(l);return tok(l,FE_TOK_ARROW,start,line,col);} if(cur(l)=='='){advance(l);return tok(l,FE_TOK_MINUS_EQ,start,line,col);} if(cur(l)=='%'){advance(l);return tok(l,FE_TOK_MINUS_WRAP,start,line,col);} return tok(l,FE_TOK_MINUS,start,line,col);
case '*': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_STAR_EQ,start,line,col);} if(cur(l)=='%'){advance(l);return tok(l,FE_TOK_STAR_WRAP,start,line,col);} return tok(l,FE_TOK_STAR,start,line,col);
case '/': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_SLASH_EQ,start,line,col);} return tok(l,FE_TOK_SLASH,start,line,col);
case '%': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_PERCENT_EQ,start,line,col);} return tok(l,FE_TOK_PERCENT,start,line,col);
case '=': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_EQEQ,start,line,col);} if(cur(l)=='>'){advance(l);return tok(l,FE_TOK_FATARROW,start,line,col);} return tok(l,FE_TOK_EQ,start,line,col);
case '!': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_NE,start,line,col);} return tok(l,FE_TOK_BANG,start,line,col);
case '<': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_LE,start,line,col);} if(cur(l)=='<'){advance(l);if(cur(l)=='='){advance(l);return tok(l,FE_TOK_SHL_EQ,start,line,col);}return tok(l,FE_TOK_SHL,start,line,col);} return tok(l,FE_TOK_LT,start,line,col);
case '>': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_GE,start,line,col);} if(cur(l)=='>'){advance(l);if(cur(l)=='='){advance(l);return tok(l,FE_TOK_SHR_EQ,start,line,col);}return tok(l,FE_TOK_SHR,start,line,col);} return tok(l,FE_TOK_GT,start,line,col);
case '&': if(cur(l)=='&'){advance(l);fe_diag_error(l->diags,here(l,line,col),"&& is not a Ferro logical operator; use 'and'");return tok(l,FE_TOK_UNKNOWN,start,line,col);} if(cur(l)=='='){advance(l);return tok(l,FE_TOK_AND_EQ,start,line,col);} return tok(l,FE_TOK_AND,start,line,col);
case '|': if(cur(l)=='|'){advance(l);fe_diag_error(l->diags,here(l,line,col),"|| is not a Ferro logical operator; use 'or'");return tok(l,FE_TOK_UNKNOWN,start,line,col);} if(cur(l)=='='){advance(l);return tok(l,FE_TOK_OR_EQ,start,line,col);} return tok(l,FE_TOK_OR,start,line,col);
case '^': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_XOR_EQ,start,line,col);} return tok(l,FE_TOK_XOR,start,line,col);
case '~': return tok(l,FE_TOK_TILDE,start,line,col);
default: fe_diag_error(l->diags,here(l,line,col),"unknown character"); return tok(l,FE_TOK_UNKNOWN,start,line,col);
}
}
const char *fe_token_name(FeTokKind k)
{
switch(k) {
case FE_TOK_EOF:return "eof"; case FE_TOK_IDENT:return "identifier"; case FE_TOK_INT:return "integer";
case FE_TOK_CHAR:return "character"; case FE_TOK_STRING:return "string"; case FE_TOK_UNIT:return "unit";
case FE_TOK_FN:return "fn"; case FE_TOK_STRUCT:return "struct"; case FE_TOK_ENUM:return "enum";
case FE_TOK_ERROR_KW:return "error"; case FE_TOK_CONST:return "const"; case FE_TOK_LET:return "let";
case FE_TOK_VAR:return "var"; case FE_TOK_IF:return "if"; case FE_TOK_ELSE:return "else";
case FE_TOK_WHILE:return "while"; case FE_TOK_FOR:return "for"; case FE_TOK_MATCH:return "match";
case FE_TOK_RETURN:return "return"; case FE_TOK_BREAK:return "break"; case FE_TOK_CONTINUE:return "continue";
case FE_TOK_TRUE:return "true"; case FE_TOK_FALSE:return "false"; case FE_TOK_NULL:return "null";
case FE_TOK_UNDEFINED:return "undefined"; case FE_TOK_AND_KW:return "and"; case FE_TOK_OR_KW:return "or";
case FE_TOK_NOT:return "not"; case FE_TOK_BANG:return "!";
case FE_TOK_TILDE:return "~";
case FE_TOK_LBRACE:return "{"; case FE_TOK_RBRACE:return "}"; case FE_TOK_LPAREN:return "("; case FE_TOK_RPAREN:return ")";
case FE_TOK_SEMI:return ";"; case FE_TOK_COLON:return ":"; case FE_TOK_COMMA:return ",";
case FE_TOK_EQ:return "="; case FE_TOK_ARROW:return "->"; case FE_TOK_FATARROW:return "=>";
default:return "token";
}
}
+50
View File
@@ -0,0 +1,50 @@
#ifndef FE_LEXER_H
#define FE_LEXER_H
#include "diag.h"
#include "arena.h"
typedef enum FeTokKind {
FE_TOK_EOF, FE_TOK_ERROR, FE_TOK_IDENT, FE_TOK_INT, FE_TOK_CHAR, FE_TOK_STRING,
FE_TOK_UNIT, FE_TOK_IMPORT, FE_TOK_PUB, FE_TOK_FN, FE_TOK_STRUCT, FE_TOK_ENUM,
FE_TOK_ERROR_KW, FE_TOK_CONST, FE_TOK_STATIC, FE_TOK_VAR, FE_TOK_LET, FE_TOK_MUT,
FE_TOK_IF, FE_TOK_ELSE, FE_TOK_WHILE, FE_TOK_FOR, FE_TOK_IN, FE_TOK_MATCH,
FE_TOK_RETURN, FE_TOK_BREAK, FE_TOK_CONTINUE, FE_TOK_DEFER, FE_TOK_UNSAFE,
FE_TOK_COMPTIME, FE_TOK_ASM, FE_TOK_TRY, FE_TOK_CATCH, FE_TOK_AS, FE_TOK_EXTERN,
FE_TOK_INTERRUPT, FE_TOK_INTERRUPT_SAFE, FE_TOK_TRUE, FE_TOK_FALSE, FE_TOK_NULL,
FE_TOK_UNDEFINED, FE_TOK_SHARED, FE_TOK_ATOMIC, FE_TOK_CRITICAL, FE_TOK_SELF,
FE_TOK_SELFTYPE, FE_TOK_TYPE, FE_TOK_PACKED, FE_TOK_ORELSE,
FE_TOK_LPAREN, FE_TOK_RPAREN, FE_TOK_LBRACE, FE_TOK_RBRACE, FE_TOK_LBRACKET, FE_TOK_RBRACKET,
FE_TOK_COMMA, FE_TOK_SEMI, FE_TOK_COLON, FE_TOK_DOT, FE_TOK_DOTDOT,
FE_TOK_PLUS, FE_TOK_MINUS, FE_TOK_STAR, FE_TOK_SLASH, FE_TOK_PERCENT,
FE_TOK_PLUS_EQ, FE_TOK_MINUS_EQ, FE_TOK_STAR_EQ, FE_TOK_SLASH_EQ, FE_TOK_PERCENT_EQ,
FE_TOK_PLUS_WRAP, FE_TOK_MINUS_WRAP, FE_TOK_STAR_WRAP,
FE_TOK_EQ, FE_TOK_EQEQ, FE_TOK_NE, FE_TOK_LT, FE_TOK_LE, FE_TOK_GT, FE_TOK_GE,
FE_TOK_AND, FE_TOK_OR, FE_TOK_AND_KW, FE_TOK_OR_KW, FE_TOK_XOR, FE_TOK_TILDE, FE_TOK_NOT, FE_TOK_BANG, FE_TOK_SHL, FE_TOK_SHR,
FE_TOK_AND_EQ, FE_TOK_OR_EQ, FE_TOK_XOR_EQ, FE_TOK_SHL_EQ, FE_TOK_SHR_EQ,
FE_TOK_ANDAND, FE_TOK_OROR, FE_TOK_ARROW, FE_TOK_FATARROW, FE_TOK_AT,
FE_TOK_QUESTION, FE_TOK_UNKNOWN
} FeTokKind;
typedef struct FeToken {
FeTokKind kind;
const char *begin;
unsigned long length;
FeLoc loc;
} FeToken;
typedef struct FeLexer {
const char *src;
unsigned long length;
unsigned long pos;
unsigned long line;
unsigned long col;
const char *file;
FeDiags *diags;
} FeLexer;
void fe_lexer_init(FeLexer *l, const char *src, unsigned long length, const char *file, FeDiags *d);
FeToken fe_lexer_next(FeLexer *l);
const char *fe_token_name(FeTokKind k);
#endif
+638
View File
@@ -0,0 +1,638 @@
#include "lowerpri.h"
void fail(Lower *L, const char *why, FeNode *n)
{
if (L->failed) return;
L->failed = 1;
fprintf(fe_diag_stream(), "%s:%lu:%lu: internal: cannot lower %s\n",
n && n->loc.file ? n->loc.file : "?",
n ? n->loc.line : 0UL, n ? n->loc.col : 0UL, why);
}
/* ---------------------------------------------------------------- types --- */
/* A Ferro type becomes what a register can hold, or a size in memory. Anything
with more than one field is memory: the backend never has to decide whether
an aggregate fits somewhere. */
FeIrType ir_type_of(const FeType *t)
{
if (!t) return FE_IR_VOID;
switch (t->kind) {
case FE_TYPE_VOID: return FE_IR_VOID;
case FE_TYPE_BOOL:
case FE_TYPE_CHAR: return FE_IR_I8;
case FE_TYPE_INT:
if (t->bits <= 8U) return FE_IR_I8;
if (t->bits <= 16U) return FE_IR_I16;
return FE_IR_I32;
case FE_TYPE_REF:
case FE_TYPE_RAW: return FE_IR_PTR;
case FE_TYPE_OWNED:
/* An owned slice carries a length beside the pointer. */
return t->elem && t->elem->kind == FE_TYPE_SLICE ? FE_IR_MEM : FE_IR_PTR;
case FE_TYPE_ENUM:
/* A payload-free enum is just its tag. */
return t->variant_count && t->fields ? FE_IR_MEM :
(t->size <= 1UL ? FE_IR_I8 :
t->size <= 2UL ? FE_IR_I16 : FE_IR_I32);
default:
return FE_IR_MEM;
}
}
int enum_has_payload(const FeType *t)
{
unsigned i;
if (!t || t->kind != FE_TYPE_ENUM) return 0;
for (i = 0; i < t->variant_count; ++i)
if (t->variants[i].field_count) return 1;
return 0;
}
FeIrType ir_type(const FeType *t)
{
if (t && t->kind == FE_TYPE_ENUM && enum_has_payload(t)) return FE_IR_MEM;
return ir_type_of(t);
}
unsigned long ir_size(const FeType *t)
{
return t ? fe_type_size(t) : 0UL;
}
unsigned ir_align(const FeType *t)
{
return t ? fe_type_align(t) : 1U;
}
int type_is_unsigned(const FeType *t)
{
return t && t->kind == FE_TYPE_INT && t->is_unsigned;
}
/* ---------------------------------------------------------------- slots --- */
Slot slot_value(unsigned temp, FeIrType t)
{
Slot s;
s.is_place = 0; s.temp = temp; s.type = t; s.size = 0;
s.place = fe_ir_at_temp(0, 0);
return s;
}
Slot slot_place(FeIrPlace p, FeIrType t, unsigned long size)
{
Slot s;
s.is_place = 1; s.temp = 0; s.place = p; s.type = t; s.size = size;
return s;
}
Slot slot_void(void)
{
return slot_value(0, FE_IR_VOID);
}
/* Read a slot as a value. An aggregate has no value form, so asking for one is
a lowering bug rather than a program error. */
unsigned as_value(Lower *L, Slot s, FeNode *n)
{
if (!s.is_place) return s.temp;
if (s.type == FE_IR_MEM) { fail(L, "an aggregate as a value", n); return 0; }
return fe_ir_load(L->m, L->b, s.type, s.place);
}
/* The address of a slot. */
unsigned as_address(Lower *L, Slot s, FeNode *n)
{
if (!s.is_place) { fail(L, "the address of a temporary", n); return 0; }
return fe_ir_addr(L->m, L->b, s.place);
}
/* --------------------------------------------------------------- locals --- */
/* Does letting go of this type have to do something? */
int needs_release(const FeType *t)
{
unsigned i;
if (!t) return 0;
if (t->kind == FE_TYPE_OWNED) return 1;
if (t->has_drop) return 1;
/* SPEC 5 R1: letting go of an owner lets go of what it owns. A struct that
holds an owner has something to do even when it says nothing itself --
which is what lets one type hold another that has a `drop`, since
calling `drop` by hand is not allowed. */
if (t->kind == FE_TYPE_STRUCT)
for (i = 0; i < t->field_count; ++i)
if (needs_release(t->fields[i].type)) return 1;
return 0;
}
int lower_reserve(Lower *L, void **items, unsigned *capacity, unsigned needed,
unsigned long item_size)
{
unsigned want;
void *grown;
if (needed < *capacity) return 1;
want = *capacity ? *capacity * 2U : 16U;
while (want <= needed) want *= 2U;
grown = fe_arena_alloc(&L->m->arena, (size_t)(want * item_size));
if (!grown) { fail(L, "a function this large", 0); return 0; }
if (*items) memcpy(grown, *items, (size_t)(*capacity * item_size));
*items = grown;
*capacity = want;
return 1;
}
unsigned declare_var(Lower *L, const char *cname, const FeType *t,
const char *name)
{
unsigned local = fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t),
ir_align(t), name);
if (lower_reserve(L, (void **)&L->vars, &L->var_capacity, L->var_count,
(unsigned long)sizeof(LowerVar))) {
L->vars[L->var_count].cname = cname;
L->vars[L->var_count].local = local;
L->vars[L->var_count].by_address = 0;
++L->var_count;
}
if (needs_release(t) &&
lower_reserve(L, (void **)&L->owed, &L->owed_capacity, L->owed_count,
(unsigned long)sizeof *L->owed)) {
unsigned flag = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "live");
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), zero, FE_IR_I8);
L->owed[L->owed_count].block = 0;
L->owed[L->owed_count].local = local;
L->owed[L->owed_count].flag = flag;
L->owed[L->owed_count].type = (FeType *)t;
++L->owed_count;
}
return local;
}
/* The liveness flag beside a local, or none. */
int release_flag(Lower *L, unsigned local, unsigned *flag)
{
unsigned i;
for (i = L->owed_count; i > 0; --i)
if (!L->owed[i - 1].block && L->owed[i - 1].local == local) {
*flag = L->owed[i - 1].flag;
return 1;
}
return 0;
}
LowerVar *find_var(Lower *L, const char *cname)
{
unsigned i;
if (!cname) return 0;
for (i = L->var_count; i > 0; --i)
if (L->vars[i - 1].cname && strcmp(L->vars[i - 1].cname, cname) == 0)
return &L->vars[i - 1];
return 0;
}
/* --------------------------------------------------------------- blocks --- */
FeIrBlock *new_block(Lower *L)
{
return fe_ir_block(L->m, L->fn);
}
/* Which file a trap raised right now came from. The whole build lowers into
one module, so the unit being lowered is the only thing that knows. */
unsigned trap_file(Lower *L)
{
return fe_ir_file(L->m, L->c->unit ? L->c->unit->path : "");
}
/* A check that must hold. `ok` is a condition; when it is false the program
stops where it is. `--no-checks` removes the comparison and the branch, not
just the message, which is the whole point of the flag. */
void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line)
{
FeIrBlock *bad = new_block(L);
FeIrBlock *cont = new_block(L);
fe_ir_br(L->b, ok, cont->id, bad->id);
L->b = bad;
fe_ir_trap(L->b, reason, line, trap_file(L));
L->b = cont;
}
/* A tag says which of the two things a wrapper holds. An optional is one byte
at the front unless the payload has a spare representation; an error union is
a two-byte error code, and zero means there is no error. */
FeIrType tag_type(const FeType *t)
{
return t && t->kind == FE_TYPE_ERROR_UNION ? FE_IR_I16 : FE_IR_I8;
}
int uses_niche(const FeType *t)
{
return t && t->kind == FE_TYPE_OPTIONAL && fe_m7_optional_uses_niche(t->elem);
}
/* Somewhere to build an aggregate that has no home of its own yet. */
unsigned scratch(Lower *L, const FeType *t, const char *why)
{
return fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t), ir_align(t), why);
}
/* The number of elements an indexable place holds, and where the first element
is. An array is its own storage; a slice points at someone else's. */
void indexable_parts(Lower *L, Slot base, const FeType *t,
unsigned *data, unsigned *length, FeNode *n)
{
if (t && t->kind == FE_TYPE_ARRAY) {
*data = as_address(L, base, n);
*length = fe_ir_const(L->m, L->b, FE_IR_I32, (long)t->length);
return;
}
if (!base.is_place) { fail(L, "a slice with no place", n); *data = 0; *length = 0; return; }
*data = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_temp(as_address(L, base, n), SLICE_PTR_OFFSET));
{
FeIrPlace lp = base.place;
lp.offset += SLICE_LEN_OFFSET;
*length = fe_ir_load(L->m, L->b, FE_IR_I32, lp);
}
}
/* ------------------------------------------------------- error codes ----- */
void note_error_name(Lower *L, const char *name)
{
unsigned i;
unsigned at;
if (!name) return;
if (!lower_reserve(L, (void **)&L->error_names, &L->error_capacity,
L->error_count, (unsigned long)sizeof(const char *)))
return;
for (i = 0; i < L->error_count; ++i)
if (!strcmp(L->error_names[i], name)) return;
/* Kept sorted as it is built, so the numbering is the spelling order. */
at = L->error_count;
while (at > 0 && strcmp(L->error_names[at - 1], name) > 0) {
L->error_names[at] = L->error_names[at - 1];
--at;
}
L->error_names[at] = name;
++L->error_count;
}
void collect_error_names(Lower *L, FeNode *n)
{
FeNode *x;
if (!n) return;
/* Allocation reports failure with a name like any other, so it has to be
in the table even though no source line writes it. */
if (n->kind == FE_N_CALL && n->a && n->a->kind == FE_N_MEMBER &&
n->a->a && n->a->a->kind == FE_N_IDENT && n->a->a->text &&
!strcmp(n->a->a->text, "mem") && n->a->b && n->a->b->text &&
(!strcmp(n->a->b->text, "create") ||
!strcmp(n->a->b->text, "alloc_slice")))
note_error_name(L, "OutOfMemory");
if (n->kind == FE_N_MEMBER && n->a && n->a->kind == FE_N_IDENT &&
n->a->text && !strcmp(n->a->text, "error") && n->b && n->b->text)
note_error_name(L, n->b->text);
collect_error_names(L, n->a);
collect_error_names(L, n->b);
collect_error_names(L, n->c);
for (x = n->children; x; x = x->next) collect_error_names(L, x);
}
long error_code(Lower *L, const char *name)
{
unsigned i;
for (i = 0; i < L->error_count; ++i)
if (!strcmp(L->error_names[i], name)) return (long)(i + 1);
return 0;
}
/* ---------------------------------------------------------- expressions --- */
FeIrOp binary_op(const char *op, int *is_cmp)
{
*is_cmp = 0;
if (!op) return FE_IR_ADD;
if (!strcmp(op, "+") || !strcmp(op, "+%")) return FE_IR_ADD;
if (!strcmp(op, "-") || !strcmp(op, "-%")) return FE_IR_SUB;
if (!strcmp(op, "*") || !strcmp(op, "*%")) return FE_IR_MUL;
if (!strcmp(op, "/")) return FE_IR_DIV;
if (!strcmp(op, "%")) return FE_IR_MOD;
if (!strcmp(op, "&")) return FE_IR_AND;
if (!strcmp(op, "|")) return FE_IR_OR;
if (!strcmp(op, "^")) return FE_IR_XOR;
if (!strcmp(op, "<<")) return FE_IR_SHL;
if (!strcmp(op, ">>")) return FE_IR_SHR;
*is_cmp = 1;
if (!strcmp(op, "==")) return FE_IR_EQ;
if (!strcmp(op, "!=")) return FE_IR_NE;
if (!strcmp(op, "<")) return FE_IR_LT;
if (!strcmp(op, "<=")) return FE_IR_LE;
if (!strcmp(op, ">")) return FE_IR_GT;
if (!strcmp(op, ">=")) return FE_IR_GE;
*is_cmp = 0;
return FE_IR_ADD;
}
long literal_value(FeNode *n)
{
const char *s = n->text;
long v = 0;
int neg = 0;
if (!s) return 0;
if (!strcmp(s, "true")) return 1;
if (!strcmp(s, "false")) return 0;
if (!strcmp(s, "null") || !strcmp(s, "undefined")) return 0;
if (*s == '\'') {
/* A character literal; the lexer kept the quotes. */
if (s[1] == '\\') {
switch (s[2]) {
case 'n': return 10;
case 't': return 9;
case 'r': return 13;
case '0': return 0;
default: return (long)(unsigned char)s[2];
}
}
return (long)(unsigned char)s[1];
}
if (*s == '-') { neg = 1; ++s; }
{
/* SPEC 3 spells four radices. Reading `0b1010` as decimal stops at the
`b` and answers zero, which is a number and so goes unnoticed. */
int base = 10;
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; }
else if (s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) { base = 2; s += 2; }
else if (s[0] == '0' && (s[1] == 'o' || s[1] == 'O')) { base = 8; s += 2; }
for (; *s; ++s) {
int d;
if (*s == '_') continue;
if (*s >= '0' && *s <= '9') d = *s - '0';
else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10;
else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10;
else break;
if (d >= base) break;
v = v * base + d;
}
}
return neg ? -v : v;
}
/* `and` and `or` do not evaluate the right side unless they have to, so they
are control flow rather than an operation. */
Slot lower_logical(Lower *L, FeNode *n, int is_and)
{
unsigned result = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "logical");
FeIrBlock *rhs = new_block(L);
FeIrBlock *join = new_block(L);
FeIrBlock *entry = L->b;
unsigned left;
unsigned right;
L->b = entry;
left = as_value(L, lower_expr(L, n->a), n->a);
fe_ir_store(L->m, L->b, fe_ir_at_local(result, 0), left, FE_IR_I8);
if (is_and) fe_ir_br(L->b, left, rhs->id, join->id);
else fe_ir_br(L->b, left, join->id, rhs->id);
L->b = rhs;
right = as_value(L, lower_expr(L, n->b), n->b);
fe_ir_store(L->m, L->b, fe_ir_at_local(result, 0), right, FE_IR_I8);
fe_ir_jmp(L->b, join->id);
L->b = join;
return slot_place(fe_ir_at_local(result, 0), FE_IR_I8, 1);
}
/* The builtins that are not calls at all: they are a constant, or they stop
the program. `@print` is expanded separately because it becomes several
calls rather than one thing. */
int lower_builtin(Lower *L, FeNode *n, Slot *out)
{
const char *name = n->text;
if (!name || name[0] != '@') return 0;
if (!strcmp(name, "@trap")) {
fe_ir_trap(L->b, FE_TRAP_EXPLICIT, n->loc.line, trap_file(L));
L->b = new_block(L);
*out = slot_void();
return 1;
}
if (!strcmp(name, "@unreachable")) {
fe_ir_trap(L->b, FE_TRAP_UNREACHABLE, n->loc.line, trap_file(L));
L->b = new_block(L);
*out = slot_void();
return 1;
}
if (!strcmp(name, "@size_of") || !strcmp(name, "@align_of")) {
FeNode *arg = n->children;
FeType *t = arg && arg->kind == FE_N_IDENT
? fe_type_intern(&L->c->types, arg->text) : 0;
long v = !strcmp(name, "@size_of") ? (long)ir_size(t)
: (long)ir_align(t);
*out = slot_value(fe_ir_const(L->m, L->b, FE_IR_I32, v), FE_IR_I32);
return 1;
}
if (!strcmp(name, "@volatile_load")) {
/* Reading through a raw pointer. Nothing here reorders loads yet, so
volatile and ordinary read the same; the keyword is what marks the
access as deliberate, and the checker already required `unsafe`. */
FeNode *arg = n->children;
unsigned p = as_value(L, lower_expr(L, arg), arg);
FeIrType t = ir_type(n->sem_type);
if (t == FE_IR_VOID || t == FE_IR_MEM) t = FE_IR_I8;
*out = slot_place(fe_ir_at_temp(p, 0), t, ir_size(n->sem_type));
return 1;
}
if (!strcmp(name, "@volatile_store")) {
FeNode *arg = n->children;
FeNode *value = arg ? arg->next : 0;
unsigned p = as_value(L, lower_expr(L, arg), arg);
Slot v = lower_expr(L, value);
FeIrType t = value && value->sem_type ? ir_type(value->sem_type)
: FE_IR_I8;
fe_ir_store(L->m, L->b, fe_ir_at_temp(p, 0), as_value(L, v, value), t);
*out = slot_void();
return 1;
}
if (!strcmp(name, "@ptr_cast")) {
/* A pointer is a pointer; the type it is said to point at is the
checker's business and leaves no trace here. */
FeNode *arg = n->children;
FeNode *value = arg ? arg->next : 0;
*out = slot_value(as_value(L, lower_expr(L, value), value), FE_IR_PTR);
return 1;
}
if (!strcmp(name, "@line")) {
*out = slot_value(fe_ir_const(L->m, L->b, FE_IR_I32,
(long)n->loc.line), FE_IR_I32);
return 1;
}
return 0;
}
/* ------------------------------------------------------------- mem.* ----- *
* The allocating intrinsics. They are not ordinary calls: `mem.create` takes a
* value and gives back an owned pointer to a copy of it, and the result is an
* error union because the allocation can fail. The runtime does the allocating;
* everything else about the shape is decided here.
* -------------------------------------------------------------------------- */
static const char *RT_ALLOC = "fe_rt_alloc";
static const char *RT_FREE = "fe_rt_free";
int is_mem_call(const FeNode *n, const char *what)
{
return n && n->a && n->a->kind == FE_N_MEMBER &&
n->a->a && n->a->a->kind == FE_N_IDENT && n->a->a->text &&
!strcmp(n->a->a->text, "mem") &&
n->a->b && n->a->b->text && !strcmp(n->a->b->text, what);
}
/* Build `!^T`: zero and the pointer when the allocation worked, the
out-of-memory code when it did not. */
Slot allocation_result(Lower *L, FeNode *n, unsigned pointer)
{
FeType *t = n->sem_type;
unsigned local = scratch(L, t, "allocated");
long payload_at = (long)fe_type_payload_offset(t);
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_PTR, 0);
unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_NE, FE_IR_PTR, pointer, zero, 1);
FeIrBlock *good = new_block(L);
FeIrBlock *bad = new_block(L);
FeIrBlock *join = new_block(L);
fe_ir_br(L->b, ok, good->id, bad->id);
L->b = good;
{
unsigned none = fe_ir_const(L->m, L->b, FE_IR_I16, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), none, FE_IR_I16);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, payload_at), pointer,
FE_IR_PTR);
}
fe_ir_jmp(L->b, join->id);
L->b = bad;
{
unsigned code = fe_ir_const(L->m, L->b, FE_IR_I16,
error_code(L, "OutOfMemory"));
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), code, FE_IR_I16);
}
fe_ir_jmp(L->b, join->id);
L->b = join;
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
int lower_mem(Lower *L, FeNode *n, Slot *out)
{
unsigned args[2];
if (is_mem_call(n, "create")) {
FeNode *arg = n->children;
FeType *value = arg ? arg->sem_type : 0;
unsigned size = fe_ir_const(L->m, L->b, FE_IR_I32,
(long)ir_size(value));
unsigned p;
Slot v;
args[0] = size;
p = fe_ir_call(L->m, L->b, FE_IR_PTR, RT_ALLOC, args, 1);
/* The value is written through the new pointer, not copied into a
local first: `create` moves what it was given. */
v = lower_expr(L, arg);
store_into(L, fe_ir_at_temp(p, 0), v, arg, ir_size(value));
*out = allocation_result(L, n, p);
return 1;
}
if (is_mem_call(n, "alloc_slice")) {
FeNode *type_arg = n->children;
FeNode *count_arg = type_arg ? type_arg->next : 0;
FeType *t = n->sem_type;
/* `!^[]T` -- the payload is an owned slice, a pointer and a length. */
FeType *owned = t ? t->error_value : 0;
FeType *slice = owned ? owned->elem : 0;
FeType *elem = slice ? slice->elem : 0;
unsigned each = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem));
unsigned howmany = count_arg
? as_value(L, lower_expr(L, count_arg), count_arg)
: fe_ir_const(L->m, L->b, FE_IR_I32, 0);
unsigned bytes = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32,
howmany, each, 1);
unsigned p;
unsigned local = scratch(L, t, "allocated");
long payload_at = (long)fe_type_payload_offset(t);
unsigned zero;
unsigned ok;
FeIrBlock *good;
FeIrBlock *bad;
FeIrBlock *join;
args[0] = bytes;
p = fe_ir_call(L->m, L->b, FE_IR_PTR, RT_ALLOC, args, 1);
zero = fe_ir_const(L->m, L->b, FE_IR_PTR, 0);
ok = fe_ir_binary(L->m, L->b, FE_IR_NE, FE_IR_PTR, p, zero, 1);
good = new_block(L);
bad = new_block(L);
join = new_block(L);
fe_ir_br(L->b, ok, good->id, bad->id);
L->b = good;
{
unsigned none = fe_ir_const(L->m, L->b, FE_IR_I16, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), none, FE_IR_I16);
fe_ir_store(L->m, L->b,
fe_ir_at_local(local, payload_at + SLICE_PTR_OFFSET),
p, FE_IR_PTR);
fe_ir_store(L->m, L->b,
fe_ir_at_local(local, payload_at + SLICE_LEN_OFFSET),
howmany, FE_IR_I32);
}
fe_ir_jmp(L->b, join->id);
L->b = bad;
{
unsigned code = fe_ir_const(L->m, L->b, FE_IR_I16,
error_code(L, "OutOfMemory"));
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), code, FE_IR_I16);
}
fe_ir_jmp(L->b, join->id);
L->b = join;
*out = slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
return 1;
}
if (is_mem_call(n, "destroy")) {
FeNode *arg = n->children;
Slot p = lower_expr(L, arg);
/* An owned slice is a pointer and a length; what was allocated is the
pointer. */
if (p.type == FE_IR_MEM) {
FeIrPlace at = p.place;
at.offset += SLICE_PTR_OFFSET;
args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, at);
} else {
args[0] = as_value(L, p, arg);
}
fe_ir_call(L->m, L->b, FE_IR_VOID, RT_FREE, args, 1);
*out = slot_void();
return 1;
}
if (is_mem_call(n, "replace")) {
/* Read what is there, put the new value in its place, hand back the
old one. This is how a value is taken out of a field without ever
leaving the field uninitialised (SPEC 5 R7). */
FeNode *dst = n->children;
FeNode *value = dst ? dst->next : 0;
FeType *t = n->sem_type;
unsigned target = as_value(L, lower_expr(L, dst), dst);
unsigned old = scratch(L, t, "replaced");
Slot fresh;
fe_ir_copy(L->m, L->b, fe_ir_at_local(old, 0), fe_ir_at_temp(target, 0),
ir_size(t));
fresh = lower_expr(L, value);
store_into(L, fe_ir_at_temp(target, 0), fresh, value, ir_size(t));
*out = slot_place(fe_ir_at_local(old, 0), ir_type(t), ir_size(t));
return 1;
}
return 0;
}
/* ------------------------------------------------------------ printing --- *
* SPEC 6.3.1: the formatting builtins are not variadic functions. A call is
* expanded here into one write per literal chunk and one per value, so the
* language never grows a variadic calling convention and the format string is
* gone by the time anything runs.
* -------------------------------------------------------------------------- */
/* Write `len` bytes of a literal that is already in the image. */
+18
View File
@@ -0,0 +1,18 @@
#ifndef FE_LOWER_H
#define FE_LOWER_H
#include "check.h"
#include "ir.h"
/* Turn the checked program into IR.
The checker leaves every expression with a type and every declaration with a
link-visible name; lowering reads those and produces the flat form the
backend wants. Everything Ferro-shaped is expanded here -- `try` becomes a
branch, `defer` is copied onto each exit path, an index becomes a comparison
and a trap -- so that neither the checker nor the backend has to know about
the other's world. */
int fe_lower_program(FeCheck *c, FeIrModule *out);
#endif
+560
View File
@@ -0,0 +1,560 @@
#include "lowerpri.h"
Slot lower_expr_core(Lower *L, FeNode *n)
{
FeType *t;
FeIrType it;
if (!n || L->failed) return slot_void();
t = n->sem_type;
it = ir_type(t);
switch (n->kind) {
case FE_N_LITERAL:
if (n->text && n->text[0] == '"') {
/* The bytes live in the image; the value is a pointer to them and
how many there are. The lexer keeps the quotes and the escapes,
so this is where `
` becomes one byte. */
char text[1024];
unsigned long raw = strlen(n->text);
unsigned long len = 0;
unsigned long i;
const char *label;
unsigned local;
unsigned p;
unsigned c;
if (raw >= 2) raw -= 2;
for (i = 0; i < raw && len + 1 < sizeof text; ++i) {
char ch = n->text[1 + i];
if (ch == 92 && i + 1 < raw) { /* a backslash */
++i;
switch (n->text[1 + i]) {
case 'n': ch = 10; break;
case 't': ch = 9; break;
case 'r': ch = 13; break;
case '0': ch = 0; break;
default: ch = n->text[1 + i]; break;
}
}
text[len++] = ch;
}
label = fe_ir_string(L->m, text, len);
if (!label) { fail(L, "a string literal", n); return slot_void(); }
local = scratch(L, t, "text");
p = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0));
fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), p,
FE_IR_PTR);
c = fe_ir_const(L->m, L->b, FE_IR_I32, (long)len);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), c,
FE_IR_I32);
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
return slot_value(fe_ir_const(L->m, L->b,
it == FE_IR_VOID ? FE_IR_I32 : it,
literal_value(n)),
it == FE_IR_VOID ? FE_IR_I32 : it);
case FE_N_IDENT: {
LowerVar *var = find_var(L, n->cname);
if (var) {
if (var->by_address) {
unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(var->local, 0));
return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t));
}
return slot_place(fe_ir_at_local(var->local, 0), it, ir_size(t));
}
if (n->cname)
return slot_place(fe_ir_at_global(n->cname, 0), it, ir_size(t));
fail(L, "an unresolved name", n);
return slot_void();
}
case FE_N_BINARY: {
int is_cmp = 0;
FeIrOp op;
unsigned a;
unsigned b;
FeIrType operand;
if (n->text && !strcmp(n->text, "orelse")) return lower_lazy(L, n, 0);
if (n->text && !strcmp(n->text, "catch")) return lower_lazy(L, n, 1);
if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or")))
return lower_logical(L, n, !strcmp(n->text, "and"));
op = binary_op(n->text, &is_cmp);
/* Comparing an optional with `null` asks about its tag, not about the
bytes of the whole wrapper -- which has no value form at all. */
if ((op == FE_IR_EQ || op == FE_IR_NE) && n->a && n->b) {
FeNode *w = fe_m7_is_null(n->b) ? n->a :
(fe_m7_is_null(n->a) ? n->b : 0);
FeType *wt = w ? w->sem_type : 0;
if (wt && wt->kind == FE_TYPE_OPTIONAL) {
Slot s = lower_expr(L, w);
unsigned t0;
unsigned z;
if (!s.is_place) {
fail(L, "an optional with no place", w);
return slot_void();
}
t0 = wrapper_tag(L, s, wt, w);
z = fe_ir_const(L->m, L->b,
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, 0);
return slot_value(fe_ir_binary(L->m, L->b, op,
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, t0, z, 1),
FE_IR_I8);
}
}
operand = ir_type(n->a ? n->a->sem_type : 0);
if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32;
a = as_value(L, lower_expr(L, n->a), n->a);
b = as_value(L, lower_expr(L, n->b), n->b);
return slot_value(fe_ir_binary(L->m, L->b, op, operand, a, b,
type_is_unsigned(n->a ? n->a->sem_type
: 0)),
is_cmp ? FE_IR_I8 : operand);
}
case FE_N_UNARY:
if (n->text && !strcmp(n->text, "try")) return lower_try(L, n);
if (n->text && !strcmp(n->text, "-")) {
unsigned zero = fe_ir_const(L->m, L->b, it, 0);
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_SUB, it, zero, v,
0), it);
}
if (n->text && !strcmp(n->text, "~")) {
/* Every bit flipped is every bit exchanged with a one. */
unsigned ones = fe_ir_const(L->m, L->b, it, -1L);
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_XOR, it, v, ones,
0), it);
}
if (n->text && !strcmp(n->text, "not")) {
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0);
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I8, v,
zero, 0), FE_IR_I8);
}
if (n->text && (!strcmp(n->text, "&") || !strcmp(n->text, "&mut"))) {
Slot inner = lower_expr(L, n->a);
return slot_value(as_address(L, inner, n->a), FE_IR_PTR);
}
fail(L, "this unary operator", n);
return slot_void();
case FE_N_MEMBER:
/* A variant used as a value carries nothing but its tag. When no
variant of the enum carries anything the whole value is that tag;
otherwise it is a tag sitting in front of an unused payload. */
if (t && t->kind == FE_TYPE_ENUM && n->b && n->b->text) {
FeVariantType *v = fe_type_variant(t, n->b->text);
if (v && !enum_has_payload(t))
return slot_value(fe_ir_const(L->m, L->b, ir_type(t),
(long)v->tag), ir_type(t));
if (v && !v->field_count) {
unsigned local = scratch(L, t, "variant");
unsigned tag = fe_ir_const(L->m, L->b, tag_type_of(t),
(long)v->tag);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), tag,
tag_type_of(t));
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM,
ir_size(t));
}
}
/* `error.Name` is a member of the open default set: a code, and
nothing to look up. */
if (n->a && n->a->kind == FE_N_IDENT && n->a->text &&
!strcmp(n->a->text, "error") && n->b && n->b->text)
return slot_value(fe_ir_const(L->m, L->b, FE_IR_I16,
error_code(L, n->b->text)),
FE_IR_I16);
/* `.?` is the payload of an optional the checker already proved is
there. */
if (n->text && !strcmp(n->text, ".?")) {
FeType *bt = n->a ? n->a->sem_type : 0;
return wrapper_payload(L, lower_expr(L, n->a), bt);
}
/* `p.^` reads through a pointer -- except for an owned slice, whose
pointer and length are the value itself, so there is nothing to
step through. */
if (n->text && !strcmp(n->text, ".^")) {
Slot base = lower_expr(L, n->a);
unsigned p;
if (base.type == FE_IR_MEM)
return slot_place(base.place, it, ir_size(t));
p = as_value(L, base, n->a);
return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t));
}
/* `.n` is how many elements there are, which an array knows at
compile time and a slice carries beside its pointer. Only for those:
a struct is free to have a field called `n`, and reading it as a
length would quietly hand back the wrong four bytes. */
if (n->b && n->b->text && !strcmp(n->b->text, "n") &&
n->a && n->a->sem_type &&
(n->a->sem_type->kind == FE_TYPE_ARRAY ||
n->a->sem_type->kind == FE_TYPE_SLICE ||
n->a->sem_type->kind == FE_TYPE_STR)) {
FeType *bt = n->a->sem_type;
Slot base;
if (bt && bt->kind == FE_TYPE_ARRAY)
return slot_value(fe_ir_const(L->m, L->b, FE_IR_I32,
(long)bt->length), FE_IR_I32);
base = lower_expr(L, n->a);
if (!base.is_place) { fail(L, "a length of a temporary", n); return slot_void(); }
base.place.offset += SLICE_LEN_OFFSET;
return slot_place(base.place, FE_IR_I32, 4);
}
/* A field is a constant offset from the base. */
{
FeType *base = n->a ? n->a->sem_type : 0;
FeFieldType *field;
Slot b;
if (base && (base->kind == FE_TYPE_REF ||
base->kind == FE_TYPE_OWNED)) base = base->elem;
field = fe_type_field(base, n->b && n->b->text ? n->b->text : "");
/* `binding.name` is not a field of anything: it is a constant or a
global in another unit, and the checker already turned it into a
link name. */
if (!field && n->cname)
return slot_place(fe_ir_at_global(n->cname, 0), it,
ir_size(t));
if (!field) { fail(L, "an unresolved field", n); return slot_void(); }
b = lower_expr(L, n->a);
if (n->a->sem_type && (n->a->sem_type->kind == FE_TYPE_REF ||
n->a->sem_type->kind == FE_TYPE_OWNED)) {
unsigned p = as_value(L, b, n->a);
return slot_place(fe_ir_at_temp(p, (long)field->offset), it,
ir_size(t));
}
if (!b.is_place) { fail(L, "a field of a temporary", n); return slot_void(); }
b.place.offset += (long)field->offset;
return slot_place(b.place, it, ir_size(t));
}
case FE_N_INDEX: {
FeType *bt = n->a ? n->a->sem_type : 0;
FeType *elem = bt ? bt->elem : 0;
Slot base;
unsigned data;
unsigned length;
unsigned index;
unsigned scale;
unsigned offset;
unsigned addr;
if (n->flags & FE_NODE_SLICE) return lower_slice(L, n);
base = lower_expr(L, n->a);
indexable_parts(L, base, bt, &data, &length, n);
index = as_value(L, lower_expr(L, n->b), n->b);
if (!L->c->no_checks) {
unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32,
index, length, 1);
guard(L, ok, FE_TRAP_BOUNDS, n->loc.line);
}
scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem));
offset = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, index, scale, 1);
addr = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, offset, 1);
return slot_place(fe_ir_at_temp(addr, 0), ir_type(elem), ir_size(elem));
}
case FE_N_ARRAY_INIT: {
unsigned local = scratch(L, t, "array");
FeType *elem = t ? t->elem : 0;
unsigned long step = ir_size(elem);
long at = 0;
FeNode *x;
for (x = n->children; x; x = x->next) {
Slot v = lower_expr(L, x);
store_into(L, fe_ir_at_local(local, at), v, x, step);
at += (long)step;
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
case FE_N_STRUCT_INIT: {
unsigned local;
FeNode *f;
/* `Enum.Variant{ .. }` builds a variant, not a struct: the tag first,
then the named fields inside the payload area. */
if (t && t->kind == FE_TYPE_ENUM && n->a && n->a->kind == FE_N_MEMBER) {
const FeVariantType *v = fe_type_variant(t,
n->a->b && n->a->b->text ? n->a->b->text : "");
long base = (long)fe_type_payload_offset(t);
unsigned tag;
if (!v) { fail(L, "an unknown variant", n); return slot_void(); }
local = scratch(L, t, "variant");
tag = fe_ir_const(L->m, L->b, tag_type_of(t), (long)v->tag);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), tag,
tag_type_of(t));
for (f = n->children; f; f = f->next) {
unsigned i;
if (f->kind != FE_N_FIELD) continue;
for (i = 0; i < v->field_count; ++i)
if (f->text && v->fields[i].name &&
!strcmp(v->fields[i].name, f->text)) break;
if (i == v->field_count) {
fail(L, "an unknown variant field", f);
return slot_void();
}
store_into(L, fe_ir_at_local(local,
base + (long)v->fields[i].offset),
lower_expr(L, f->a), f, ir_size(v->fields[i].type));
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
local = scratch(L, t, "struct");
for (f = n->children; f; f = f->next) {
FeFieldType *field;
Slot v;
if (f->kind != FE_N_FIELD) continue;
field = fe_type_field(t, f->text);
if (!field) { fail(L, "an unresolved field", f); return slot_void(); }
v = lower_expr(L, f->a);
store_into(L, fe_ir_at_local(local, (long)field->offset), v, f,
ir_size(field->type));
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
case FE_N_CALL:
return lower_call(L, n);
case FE_N_TYPE:
/* `x as T`: the operand is `a` and the target type is the node's own.
Between integers this only changes how wide the value is and whether
the top bits repeat the sign. */
if (n->a) {
FeType *from = n->a->sem_type;
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
if (ir_type(from) == it) return slot_value(v, it);
return slot_value(fe_ir_cast(L->m, L->b, ir_type(from), it, v,
type_is_unsigned(from)), it);
}
fail(L, "this type expression", n);
return slot_void();
case FE_N_EXPR:
return lower_expr(L, n->a);
default:
fail(L, "this expression", n);
return slot_void();
}
}
/* The link name of the `drop` method for this type, found through the instance
the checker recorded. */
const char *drop_name(Lower *L, const FeType *t)
{
unsigned i;
FeNode *method = 0;
if (!t || !t->decl_node) return 0;
for (method = t->decl_node->children; method; method = method->next)
if (method->kind == FE_N_FN && method->text &&
!strcmp(method->text, "drop")) break;
if (!method) return 0;
for (i = 0; i < L->c->instance_count; ++i)
if (L->c->instances[i].decl == method &&
L->c->instances[i].owner == t)
return L->c->instances[i].cname;
return method->cname;
}
/* Let go of one value sitting at `at`. A type that says how to let go of
itself is asked first; then whatever it holds is let go of in turn, so a
struct that owns a struct that owns a buffer settles all three without
anyone writing a `drop` (SPEC 5 R1). */
void release_at(Lower *L, const FeType *t, FeIrPlace at)
{
unsigned args[1];
unsigned i;
if (!t) return;
if (t->has_drop) {
const char *how = drop_name(L, t);
args[0] = fe_ir_addr(L->m, L->b, at);
if (how) fe_ir_call(L->m, L->b, FE_IR_VOID, how, args, 1);
}
if (t->kind == FE_TYPE_OWNED) {
FeIrPlace p = at;
if (t->elem && t->elem->kind == FE_TYPE_SLICE)
p.offset += SLICE_PTR_OFFSET;
args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, p);
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1);
return;
}
if (t->kind == FE_TYPE_STRUCT)
for (i = 0; i < t->field_count; ++i) {
FeIrPlace p = at;
if (!needs_release(t->fields[i].type)) continue;
p.offset += (long)t->fields[i].offset;
release_at(L, t->fields[i].type, p);
}
}
/* Settle what a scope owes, most recent first. A `return` in the middle of a
function still owes everything, so every exit path calls this. */
void run_deferred(Lower *L, unsigned from)
{
unsigned i;
for (i = L->owed_count; i > from; --i) {
if (L->owed[i - 1].block) {
lower_stmt(L, L->owed[i - 1].block);
continue;
}
{
/* Release only where the value is still here. */
unsigned live = fe_ir_load(L->m, L->b, FE_IR_I8,
fe_ir_at_local(L->owed[i - 1].flag, 0));
FeIrBlock *doit = new_block(L);
FeIrBlock *skip = new_block(L);
FeType *t = L->owed[i - 1].type;
fe_ir_br(L->b, live, doit->id, skip->id);
L->b = doit;
release_at(L, t, fe_ir_at_local(L->owed[i - 1].local, 0));
fe_ir_jmp(L->b, skip->id);
L->b = skip;
}
}
}
/* ------------------------------------------------------- wrappers -------- *
* An optional is a tag and a payload; an error union is an error code and a
* payload, where a code of zero means there is no error. Both are memory, and
* both are built the same way: write the tag, then write the value after it.
* -------------------------------------------------------------------------- */
Slot wrap_context(Lower *L, Slot v, FeNode *n)
{
FeType *want = n->sem_context;
unsigned local;
long payload_at;
if (!want) return v;
local = scratch(L, want, "wrapped");
payload_at = (long)fe_type_payload_offset(want);
if (want->kind == FE_TYPE_OPTIONAL) {
if (fe_m7_is_null(n)) {
/* A payload with a spare representation uses it for "nothing"
instead of carrying a separate tag. */
unsigned z = fe_ir_const(L->m, L->b,
uses_niche(want) ? FE_IR_PTR : FE_IR_I8, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), z,
uses_niche(want) ? FE_IR_PTR : FE_IR_I8);
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want));
}
if (!uses_niche(want)) {
unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), one, FE_IR_I8);
}
store_into(L, fe_ir_at_local(local, payload_at), v, n,
ir_size(want->elem));
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want));
}
if (want->kind == FE_TYPE_ERROR_UNION) {
FeType *value_type = want->error_value;
if (n->sem_type && n->sem_type->is_error) {
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0),
as_value(L, v, n), FE_IR_I16);
} else {
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), zero, FE_IR_I16);
if (value_type && value_type->kind != FE_TYPE_VOID)
store_into(L, fe_ir_at_local(local, payload_at), v, n,
ir_size(value_type));
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want));
}
return v;
}
/* The tag of a wrapper that is already in memory. */
unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n)
{
FeIrPlace p;
if (!w.is_place) { fail(L, "a wrapper with no place", n); return 0; }
p = w.place;
if (uses_niche(t)) return fe_ir_load(L->m, L->b, FE_IR_PTR, p);
return fe_ir_load(L->m, L->b, tag_type(t), p);
}
Slot wrapper_payload(Lower *L, Slot w, const FeType *t)
{
FeType *payload = t ? (t->kind == FE_TYPE_ERROR_UNION ? t->error_value
: t->elem) : 0;
FeIrPlace p = w.place;
(void)L;
p.offset += (long)fe_type_payload_offset(t);
return slot_place(p, ir_type(payload), ir_size(payload));
}
/* Leave the function with this error code, after the deferred blocks. */
void return_error(Lower *L, unsigned err, FeNode *n)
{
FeType *ret = L->ret_type;
unsigned local = scratch(L, ret, "failure");
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), err, FE_IR_I16);
run_deferred(L, 0);
if (L->fn->returns_by_address) {
unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(L->ret_local, 0));
fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0), fe_ir_at_local(local, 0),
ir_size(ret));
fe_ir_ret(L->b, 0, 0);
return;
}
fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(ret),
fe_ir_at_local(local, 0)), 1);
(void)n;
}
/* `try e` -- if e failed, leave with its error; otherwise the value. */
Slot lower_try(Lower *L, FeNode *n)
{
FeType *t = n->a ? n->a->sem_type : 0;
Slot e = lower_expr(L, n->a);
unsigned err = wrapper_tag(L, e, t, n);
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0);
unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I16, err, zero, 1);
FeIrBlock *bad = new_block(L);
FeIrBlock *good = new_block(L);
fe_ir_br(L->b, ok, good->id, bad->id);
L->b = bad;
return_error(L, err, n);
L->b = good;
return wrapper_payload(L, e, t);
}
/* `e orelse d` and `e catch d` both mean "the value, or that instead". The
right-hand side is only evaluated when it is needed, so it is a branch. */
Slot lower_lazy(Lower *L, FeNode *n, int is_catch)
{
FeType *t = n->a ? n->a->sem_type : 0;
FeType *payload = t ? (is_catch ? t->error_value : t->elem) : 0;
Slot e;
unsigned tag;
unsigned zero;
unsigned ok;
unsigned result;
FeIrBlock *other;
FeIrBlock *join;
FeIrBlock *have;
e = lower_expr(L, n->a);
tag = wrapper_tag(L, e, t, n);
zero = fe_ir_const(L->m, L->b, is_catch || uses_niche(t) ? FE_IR_PTR
: FE_IR_I8, 0);
/* An error union is fine when its code is zero; an optional is fine when
its tag is not. */
ok = fe_ir_binary(L->m, L->b, is_catch ? FE_IR_EQ : FE_IR_NE,
is_catch ? FE_IR_I16 : (uses_niche(t) ? FE_IR_PTR
: FE_IR_I8),
tag, zero, 1);
result = scratch(L, payload, "result");
have = new_block(L);
other = new_block(L);
join = new_block(L);
fe_ir_br(L->b, ok, have->id, other->id);
L->b = have;
store_into(L, fe_ir_at_local(result, 0), wrapper_payload(L, e, t), n,
ir_size(payload));
fe_ir_jmp(L->b, join->id);
L->b = other;
if (is_catch && n->c) {
/* The block form handles the error and must not fall through with a
value, so whatever it leaves behind is what the checker allowed. */
lower_stmt(L, n->c);
} else {
Slot d = lower_expr(L, n->b);
store_into(L, fe_ir_at_local(result, 0), d, n->b, ir_size(payload));
}
fe_ir_jmp(L->b, join->id);
L->b = join;
return slot_place(fe_ir_at_local(result, 0), ir_type(payload),
ir_size(payload));
}
/* ----------------------------------------------------------- statements --- */
+171
View File
@@ -0,0 +1,171 @@
#ifndef FE_LOWERPRI_H
#define FE_LOWERPRI_H
/* Lowering's own vocabulary, shared by the files it is split across. */
#include "lower.h"
#include "m7.h"
#include "own.h"
#include <string.h>
#include <stdio.h>
#include <string.h>
#include "m7.h"
#include "own.h"
#include <stdio.h>
/* ------------------------------------------------------------------------- *
* Lowering
*
* One function at a time, one statement at a time. A `Slot` is what an
* expression produced: either a value already in a temporary, or a place in
* memory that a value can be read from or written to. Aggregates are always
* places -- they are never carried in a temporary, because a temporary is a
* register and an aggregate does not fit in one.
* ------------------------------------------------------------------------- */
typedef struct LowerVar {
const char *cname;
unsigned local;
/* An aggregate parameter arrives as an address, so the slot holds a
pointer and the value is one dereference away. */
int by_address;
} LowerVar;
typedef struct Lower {
FeCheck *c;
FeIrModule *m;
FeIrFunc *fn;
FeIrBlock *b; /* the block being appended to */
FeType *ret_type;
unsigned ret_local; /* hidden result address, when returning mem */
/* These three grow. A fixed size here does not report a program that is
too big -- it quietly drops what does not fit and generates wrong code,
which is the worst way for a limit to be reached. */
LowerVar *vars;
unsigned var_count;
unsigned var_capacity;
/* Loop targets, for break and continue. */
unsigned break_target[32];
unsigned continue_target[32];
unsigned loop_depth;
/* What a scope still owes when it ends: `defer` blocks to run and owned
values to release, in the order they were written. Every exit path runs
what is live, last first.
A drop carries a flag beside the value. The flag is set when the value
is stored and cleared wherever it is moved away, so the release happens
exactly on the paths where the value is still there -- which is not
something the shape of the code can tell you on its own. */
struct {
FeNode *block; /* a `defer`, when set */
unsigned local; /* the owned value, otherwise */
unsigned flag;
FeType *type;
} *owed;
unsigned owed_count;
unsigned owed_capacity;
/* Every `error.Name` used anywhere in the build, sorted, numbered from one.
SPEC 4.6: the names are collected rather than declared, and the order is
fixed by the spelling so that the same program always gets the same
codes however the build was ordered. */
const char **error_names;
unsigned error_count;
unsigned error_capacity;
int failed;
} Lower;
typedef struct Slot {
int is_place;
unsigned temp; /* the value, when is_place is 0 */
FeIrPlace place; /* where it lives, when is_place is 1 */
FeIrType type;
unsigned long size; /* for FE_IR_MEM */
} Slot;
/* A slice is a pointer and a length, in that order. Both the compiler and the
runtime read it this way, so the offsets live here and nowhere else. */
#define SLICE_PTR_OFFSET 0L
#define SLICE_LEN_OFFSET 4L
/* Grow one of the checker's own arrays. Returns zero when there is no more
memory, which the caller reports rather than ignores. */
int lower_reserve(Lower *L, void **items, unsigned *capacity, unsigned needed,
unsigned long item_size);
/* Every definition in lowering, so the split files can see each other. */
FeIrType tag_type_of(const FeType *t);
int struct_is_generic(const FeNode *decl);
void lower_if_let(Lower *L, FeNode *n);
unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n);
void bind_payload(Lower *L, Slot subject, const FeType *t,
const FeVariantType *v, FeNode *arm);
void fail(Lower *L, const char *why, FeNode *n);
FeIrType ir_type_of(const FeType *t);
int enum_has_payload(const FeType *t);
FeIrType ir_type(const FeType *t);
unsigned long ir_size(const FeType *t);
unsigned ir_align(const FeType *t);
int type_is_unsigned(const FeType *t);
Slot slot_value(unsigned temp, FeIrType t);
Slot slot_place(FeIrPlace p, FeIrType t, unsigned long size);
Slot slot_void(void);
unsigned as_value(Lower *L, Slot s, FeNode *n);
unsigned as_address(Lower *L, Slot s, FeNode *n);
int needs_release(const FeType *t);
unsigned declare_var(Lower *L, const char *cname, const FeType *t,
const char *name);
int release_flag(Lower *L, unsigned local, unsigned *flag);
LowerVar *find_var(Lower *L, const char *cname);
FeIrBlock *new_block(Lower *L);
unsigned trap_file(Lower *L);
void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line);
FeIrType tag_type(const FeType *t);
int uses_niche(const FeType *t);
unsigned scratch(Lower *L, const FeType *t, const char *why);
void indexable_parts(Lower *L, Slot base, const FeType *t,
unsigned *data, unsigned *length, FeNode *n);
void note_error_name(Lower *L, const char *name);
void collect_error_names(Lower *L, FeNode *n);
long error_code(Lower *L, const char *name);
FeIrOp binary_op(const char *op, int *is_cmp);
long literal_value(FeNode *n);
Slot lower_logical(Lower *L, FeNode *n, int is_and);
int lower_builtin(Lower *L, FeNode *n, Slot *out);
int is_mem_call(const FeNode *n, const char *what);
Slot allocation_result(Lower *L, FeNode *n, unsigned pointer);
int lower_mem(Lower *L, FeNode *n, Slot *out);
void emit_text(Lower *L, unsigned handle, const char *text,
unsigned long len);
void emit_value_text(Lower *L, unsigned handle, FeNode *arg, int verb);
int lower_print(Lower *L, FeNode *n, Slot *out);
Slot lower_call(Lower *L, FeNode *n);
Slot lower_expr(Lower *L, FeNode *n);
Slot lower_expr_core(Lower *L, FeNode *n);
const char *drop_name(Lower *L, const FeType *t);
void release_at(Lower *L, const FeType *t, FeIrPlace at);
void run_deferred(Lower *L, unsigned from);
Slot wrap_context(Lower *L, Slot v, FeNode *n);
unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n);
Slot wrapper_payload(Lower *L, Slot w, const FeType *t);
void return_error(Lower *L, unsigned err, FeNode *n);
Slot lower_try(Lower *L, FeNode *n);
Slot lower_lazy(Lower *L, FeNode *n, int is_catch);
void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n,
unsigned long size);
void lower_return(Lower *L, FeNode *n);
void lower_if(Lower *L, FeNode *n);
void lower_while(Lower *L, FeNode *n);
Slot lower_slice(Lower *L, FeNode *n);
void lower_for(Lower *L, FeNode *n);
void lower_match(Lower *L, FeNode *n);
void lower_stmt(Lower *L, FeNode *n);
void lower_global(Lower *L, FeNode *n);
int fn_is_generic(const FeNode *fn);
void lower_fn_as(Lower *L, FeNode *fn, const char *name);
void lower_fn(Lower *L, FeNode *fn);
#endif
+257
View File
@@ -0,0 +1,257 @@
#include "lowerpri.h"
void emit_text(Lower *L, unsigned handle, const char *text,
unsigned long len)
{
unsigned args[3];
const char *label;
if (!len) return;
label = fe_ir_string(L->m, text, len);
if (!label) return;
args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle);
args[1] = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0));
args[2] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)len);
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write", args, 3);
}
/* Write one value, the way the verb asked for. */
void emit_value_text(Lower *L, unsigned handle, FeNode *arg, int verb)
{
FeType *t = arg ? arg->sem_type : 0;
Slot v = lower_expr(L, arg);
unsigned args[3];
if (t && (t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR)) {
FeIrPlace at = v.place;
if (!v.is_place) { fail(L, "text with no place", arg); return; }
args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle);
at.offset = v.place.offset + SLICE_PTR_OFFSET;
args[1] = fe_ir_load(L->m, L->b, FE_IR_PTR, at);
at.offset = v.place.offset + SLICE_LEN_OFFSET;
args[2] = fe_ir_load(L->m, L->b, FE_IR_I32, at);
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write", args, 3);
return;
}
if (t && t->kind == FE_TYPE_BOOL) {
/* Two literals and a branch: cheaper than a runtime that knows about
Ferro's names for truth. */
FeIrBlock *yes = new_block(L);
FeIrBlock *no = new_block(L);
FeIrBlock *join = new_block(L);
fe_ir_br(L->b, as_value(L, v, arg), yes->id, no->id);
L->b = yes;
emit_text(L, handle, "true", 4);
fe_ir_jmp(L->b, join->id);
L->b = no;
emit_text(L, handle, "false", 5);
fe_ir_jmp(L->b, join->id);
L->b = join;
return;
}
if (verb == 'c' || (t && t->kind == FE_TYPE_CHAR)) {
/* One byte, written from a slot of its own so it has an address. */
unsigned cell = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "char");
fe_ir_store(L->m, L->b, fe_ir_at_local(cell, 0), as_value(L, v, arg),
FE_IR_I8);
args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle);
args[1] = fe_ir_addr(L->m, L->b, fe_ir_at_local(cell, 0));
args[2] = fe_ir_const(L->m, L->b, FE_IR_I32, 1);
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write", args, 3);
return;
}
args[0] = fe_ir_const(L->m, L->b, FE_IR_I32, (long)handle);
args[1] = as_value(L, v, arg);
if (verb == 'x') {
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write_hex", args, 2);
return;
}
args[2] = fe_ir_const(L->m, L->b, FE_IR_I32,
type_is_unsigned(t) || (t && t->kind == FE_TYPE_ENUM)
? 1 : 0);
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_write_int", args, 3);
}
/* `@print(fmt, ...)`, `@fprint(w, fmt, ...)`. The checker has already agreed
that the string is a literal and that the count matches. */
int lower_print(Lower *L, FeNode *n, Slot *out)
{
const char *name = n->text;
int to_writer;
unsigned handle;
FeNode *fmt;
FeNode *arg;
const char *text;
unsigned long raw;
unsigned long i;
unsigned long chunk;
char plain[1024];
unsigned long plain_len;
if (!name || (strcmp(name, "@print") != 0 && strcmp(name, "@fprint") != 0))
return 0;
to_writer = strcmp(name, "@fprint") == 0;
fmt = n->children;
if (to_writer) {
/* A Writer is a handle; Stdout is 1 and Stderr is 2 (std.io). */
Slot w = lower_expr(L, fmt);
handle = 0;
(void)w;
fmt = fmt ? fmt->next : 0;
}
handle = to_writer ? 2 : 1;
if (!fmt || !fmt->text || fmt->text[0] != '"') {
fail(L, "a format string that is not a literal", n);
*out = slot_void();
return 1;
}
text = fmt->text + 1;
raw = strlen(fmt->text);
if (raw >= 2) raw -= 2;
arg = fmt->next;
plain_len = 0;
chunk = 0;
(void)chunk;
for (i = 0; i < raw; ++i) {
char ch = text[i];
if (ch == 92 && i + 1 < raw) { /* an escape */
++i;
switch (text[i]) {
case 'n': ch = 10; break;
case 't': ch = 9; break;
case 'r': ch = 13; break;
case '0': ch = 0; break;
default: ch = text[i]; break;
}
if (plain_len + 1 < sizeof plain) plain[plain_len++] = ch;
continue;
}
if (ch == '{') {
int verb = ' ';
unsigned long close = i + 1;
while (close < raw && text[close] != '}') ++close;
if (close == i + 2) verb = text[i + 1];
emit_text(L, handle, plain, plain_len);
plain_len = 0;
emit_value_text(L, handle, arg, verb);
if (arg) arg = arg->next;
i = close;
continue;
}
if (ch == '}') continue; /* `}}` is one brace */
if (plain_len + 1 < sizeof plain) plain[plain_len++] = ch;
}
emit_text(L, handle, plain, plain_len);
*out = slot_void();
return 1;
}
Slot lower_call(Lower *L, FeNode *n)
{
unsigned args[16];
unsigned count = 0;
FeNode *arg = n->children;
FeType *ret = n->sem_type;
FeIrType rt = ir_type(ret);
unsigned result_local = 0;
const char *callee = n->a && n->a->cname ? n->a->cname :
(n->sem_decl && n->sem_decl->cname ?
n->sem_decl->cname : 0);
{
Slot built;
if (lower_builtin(L, n, &built)) return built;
if (lower_mem(L, n, &built)) return built;
if (lower_print(L, n, &built)) return built;
}
/* `Enum.Variant(payload)` is a constructor, not a call. */
if (ret && ret->kind == FE_TYPE_ENUM && n->a && n->a->kind == FE_N_MEMBER &&
n->a->b && n->a->b->text) {
const FeVariantType *v = fe_type_variant(ret, n->a->b->text);
if (v) {
unsigned local = scratch(L, ret, "variant");
unsigned tag = fe_ir_const(L->m, L->b, tag_type_of(ret),
(long)v->tag);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), tag,
tag_type_of(ret));
if (v->field_count && n->children)
store_into(L,
fe_ir_at_local(local,
(long)fe_type_payload_offset(ret) +
(long)v->fields[0].offset),
lower_expr(L, n->children), n->children,
ir_size(v->fields[0].type));
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM,
ir_size(ret));
}
}
if (!callee) { fail(L, "a call with no target", n); return slot_void(); }
/* An aggregate result is written through a hidden first argument. */
if (rt == FE_IR_MEM) {
result_local = fe_ir_local(L->m, L->fn, FE_IR_MEM, ir_size(ret),
ir_align(ret), "result");
args[count++] = fe_ir_addr(L->m, L->b, fe_ir_at_local(result_local, 0));
}
/* A method call passes what it was reached through as its first argument.
`self: Self` and `self: &Self` are the same thing here: the address of
the receiver, because an aggregate never travels in a register. */
if (n->a && n->a->kind == FE_N_MEMBER && n->sem_decl) {
FeNode *first = n->sem_decl->a ? n->sem_decl->a->children : 0;
if (first && first->text && !strcmp(first->text, "self")) {
FeType *rt = n->a->a ? n->a->a->sem_type : 0;
Slot recv = lower_expr(L, n->a->a);
/* A receiver that is already a reference or an owner is a pointer
already; taking its address would pass a pointer to the
pointer. */
if (rt && (rt->kind == FE_TYPE_REF ||
(rt->kind == FE_TYPE_OWNED && ir_type(rt) == FE_IR_PTR)))
args[count++] = as_value(L, recv, n->a->a);
else
args[count++] = recv.is_place ? as_address(L, recv, n->a->a)
: recv.temp;
}
}
/* A generic call passes its type arguments first. They were consumed when
the instance was chosen and carry no value, so they are not passed. */
{
FeNode *p;
for (p = n->sem_decl && n->sem_decl->a ? n->sem_decl->a->children : 0;
p && arg; p = p->next) {
if (!(p->flags & FE_NODE_COMPTIME)) break;
arg = arg->next;
}
}
for (; arg; arg = arg->next) {
Slot a = lower_expr(L, arg);
if (count >= 16) { fail(L, "too many arguments", n); break; }
args[count++] = a.type == FE_IR_MEM ? as_address(L, a, arg)
: as_value(L, a, arg);
}
if (rt == FE_IR_MEM) {
fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count);
return slot_place(fe_ir_at_local(result_local, 0), FE_IR_MEM,
ir_size(ret));
}
if (rt == FE_IR_VOID) {
fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count);
return slot_void();
}
return slot_value(fe_ir_call(L->m, L->b, rt, callee, args, count), rt);
}
/* Every expression may be standing where a wrapper is expected, so the wrap is
applied once, here, rather than at each place that could need it. */
Slot lower_expr(Lower *L, FeNode *n)
{
Slot v;
if (!n || L->failed) return slot_void();
v = lower_expr_core(L, n);
/* The checker marked the uses that hand ownership away. Where one names a
local we track, the value is no longer ours to release. */
if ((n->flags & FE_OWN_NODE_CONSUMED) && n->kind == FE_N_IDENT) {
LowerVar *var = find_var(L, n->cname);
unsigned flag;
if (var && release_flag(L, var->local, &flag)) {
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), zero, FE_IR_I8);
}
}
return n->sem_context ? wrap_context(L, v, n) : v;
}
+706
View File
@@ -0,0 +1,706 @@
#include "lowerpri.h"
void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n,
unsigned long size)
{
if (value.type == FE_IR_MEM) {
if (!value.is_place) { fail(L, "an aggregate value", n); return; }
fe_ir_copy(L->m, L->b, dst, value.place, size);
return;
}
/* How wide the store is belongs to the place, not to the value. An
integer literal is `i32` until something narrower asks for it, so
`let b: u8 = 200;` arrives here as four bytes going into one -- and
writing four wipes out whatever the frame put next to it. */
fe_ir_store(L->m, L->b, dst, as_value(L, value, n),
size == 1UL ? FE_IR_I8 :
size == 2UL ? FE_IR_I16 : value.type);
}
void lower_return(Lower *L, FeNode *n)
{
Slot v;
if (!n->a) {
/* A bare return from a `!void` function still has to say that nothing
went wrong. */
if (L->ret_type && L->ret_type->kind == FE_TYPE_ERROR_UNION) {
unsigned local = scratch(L, L->ret_type, "success");
unsigned none = fe_ir_const(L->m, L->b, FE_IR_I16, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), none, FE_IR_I16);
run_deferred(L, 0);
if (L->fn->returns_by_address) {
unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(L->ret_local, 0));
fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0),
fe_ir_at_local(local, 0), ir_size(L->ret_type));
fe_ir_ret(L->b, 0, 0);
return;
}
fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(L->ret_type),
fe_ir_at_local(local, 0)), 1);
return;
}
run_deferred(L, 0);
fe_ir_ret(L->b, 0, 0);
return;
}
/* The value is computed before the deferred blocks run, because they may
destroy what it was read from. */
v = lower_expr(L, n->a);
if (v.type != FE_IR_MEM && v.is_place)
v = slot_value(as_value(L, v, n->a), v.type);
run_deferred(L, 0);
if (L->fn->returns_by_address) {
unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(L->ret_local, 0));
store_into(L, fe_ir_at_temp(dst, 0), v, n, ir_size(L->ret_type));
fe_ir_ret(L->b, 0, 0);
return;
}
fe_ir_ret(L->b, as_value(L, v, n->a), 1);
}
void lower_if(Lower *L, FeNode *n)
{
FeIrBlock *then_b = new_block(L);
FeIrBlock *else_b = n->c ? new_block(L) : 0;
FeIrBlock *join = new_block(L);
unsigned cond = as_value(L, lower_expr(L, n->a), n->a);
fe_ir_br(L->b, cond, then_b->id, else_b ? else_b->id : join->id);
L->b = then_b;
lower_stmt(L, n->b);
fe_ir_jmp(L->b, join->id);
if (else_b) {
L->b = else_b;
lower_stmt(L, n->c);
fe_ir_jmp(L->b, join->id);
}
L->b = join;
}
void lower_while(Lower *L, FeNode *n)
{
FeIrBlock *head = new_block(L);
FeIrBlock *body = new_block(L);
FeIrBlock *done = new_block(L);
unsigned cond;
fe_ir_jmp(L->b, head->id);
L->b = head;
cond = as_value(L, lower_expr(L, n->a), n->a);
fe_ir_br(L->b, cond, body->id, done->id);
if (L->loop_depth < 32) {
L->break_target[L->loop_depth] = done->id;
L->continue_target[L->loop_depth] = head->id;
++L->loop_depth;
}
L->b = body;
lower_stmt(L, n->b);
fe_ir_jmp(L->b, head->id);
if (L->loop_depth) --L->loop_depth;
L->b = done;
}
/* `x[a..b]` makes a pointer and a length out of part of something indexable.
Both ends are checked -- against each other and against what is there --
before the pointer is formed. An empty slice of a valid range is fine; one
that starts past its end is not. */
Slot lower_slice(Lower *L, FeNode *n)
{
FeType *bt = n->a ? n->a->sem_type : 0;
FeType *elem = bt ? bt->elem : 0;
FeType *t = n->sem_type;
Slot base = lower_expr(L, n->a);
unsigned data;
unsigned length;
unsigned from;
unsigned to;
unsigned local;
unsigned scale;
unsigned off;
unsigned at;
unsigned count;
indexable_parts(L, base, bt, &data, &length, n);
from = n->b ? as_value(L, lower_expr(L, n->b), n->b)
: fe_ir_const(L->m, L->b, FE_IR_I32, 0);
to = n->c ? as_value(L, lower_expr(L, n->c), n->c) : length;
if (!L->c->no_checks) {
unsigned ordered = fe_ir_binary(L->m, L->b, FE_IR_LE, FE_IR_I32,
from, to, 1);
unsigned within;
guard(L, ordered, FE_TRAP_BOUNDS, n->loc.line);
within = fe_ir_binary(L->m, L->b, FE_IR_LE, FE_IR_I32, to, length, 1);
guard(L, within, FE_TRAP_BOUNDS, n->loc.line);
}
scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem));
off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, from, scale, 1);
at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, off, 1);
count = fe_ir_binary(L->m, L->b, FE_IR_SUB, FE_IR_I32, to, from, 1);
local = scratch(L, t, "slice");
fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), at,
FE_IR_PTR);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), count,
FE_IR_I32);
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
/* Three shapes share the keyword.
for i in a..b { } counts
for x in thing { } walks, binding a reference to each element
for i, x in thing { } walks, binding the position as well
The count is read once before the body, so a thing that grows underneath the
loop cannot walk past what was measured. The element binding is a reference
(`x.^` reads it), which is what lets a loop write back into the thing. */
void lower_for(Lower *L, FeNode *n)
{
FeIrBlock *head;
FeIrBlock *body;
FeIrBlock *step;
FeIrBlock *done;
unsigned counter;
unsigned limit;
if (n->c) {
/* The counting form: the variable is the count itself. */
unsigned from = as_value(L, lower_expr(L, n->a), n->a);
unsigned to;
counter = declare_var(L, n->cname, 0, n->text);
L->fn->locals[counter].type = FE_IR_I32;
L->fn->locals[counter].size = 4;
L->fn->locals[counter].align = 4;
fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), from, FE_IR_I32);
to = as_value(L, lower_expr(L, n->c), n->c);
limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "limit");
fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), to, FE_IR_I32);
head = new_block(L);
body = new_block(L);
step = new_block(L);
done = new_block(L);
fe_ir_jmp(L->b, head->id);
L->b = head;
{
unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32,
fe_ir_at_local(counter, 0));
unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32,
fe_ir_at_local(limit, 0));
unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1);
fe_ir_br(L->b, more, body->id, done->id);
}
} else {
FeType *bt = n->a ? n->a->sem_type : 0;
FeType *elem = bt ? bt->elem : 0;
Slot base = lower_expr(L, n->a);
unsigned data;
unsigned length;
unsigned data_local;
unsigned item;
indexable_parts(L, base, bt, &data, &length, n);
data_local = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, "data");
fe_ir_store(L->m, L->b, fe_ir_at_local(data_local, 0), data, FE_IR_PTR);
limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "count");
fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), length, FE_IR_I32);
/* With two names the first is the position and the second the element;
with one it is the element. */
counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index");
if (n->aux_cname) {
(void)lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count,
(unsigned long)sizeof(LowerVar));
L->vars[L->var_count].cname = n->cname;
L->vars[L->var_count].local = counter;
L->vars[L->var_count].by_address = 0;
++L->var_count;
item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->aux_text);
(void)lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count,
(unsigned long)sizeof(LowerVar));
L->vars[L->var_count].cname = n->aux_cname;
L->vars[L->var_count].local = item;
L->vars[L->var_count].by_address = 0;
++L->var_count;
} else {
item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->text);
(void)lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count,
(unsigned long)sizeof(LowerVar));
L->vars[L->var_count].cname = n->cname;
L->vars[L->var_count].local = item;
L->vars[L->var_count].by_address = 0;
++L->var_count;
}
{
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I32, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), zero, FE_IR_I32);
}
head = new_block(L);
body = new_block(L);
step = new_block(L);
done = new_block(L);
fe_ir_jmp(L->b, head->id);
L->b = head;
{
unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32,
fe_ir_at_local(counter, 0));
unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32,
fe_ir_at_local(limit, 0));
unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1);
fe_ir_br(L->b, more, body->id, done->id);
}
L->b = body;
{
unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32,
fe_ir_at_local(counter, 0));
unsigned scale = fe_ir_const(L->m, L->b, FE_IR_I32,
(long)ir_size(elem));
unsigned off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, i,
scale, 1);
unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(data_local, 0));
unsigned at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, p,
off, 1);
fe_ir_store(L->m, L->b, fe_ir_at_local(item, 0), at, FE_IR_PTR);
}
L->b = head;
}
if (L->loop_depth < 32) {
L->break_target[L->loop_depth] = done->id;
L->continue_target[L->loop_depth] = step->id;
++L->loop_depth;
}
L->b = body;
lower_stmt(L, n->b);
fe_ir_jmp(L->b, step->id);
L->b = step;
{
unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32,
fe_ir_at_local(counter, 0));
unsigned one = fe_ir_const(L->m, L->b, FE_IR_I32, 1);
unsigned next = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_I32, i, one, 1);
fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), next, FE_IR_I32);
}
fe_ir_jmp(L->b, head->id);
if (L->loop_depth) --L->loop_depth;
L->b = done;
}
/* `match` over a payload-free enum or an integer: compare the tag against each
arm's pattern in turn. The checker already proved the arms cover everything,
so falling off the end cannot happen in a program that compiled -- but the
generated code has to go somewhere, and going to the join is right. */
/* The width of a tag: an enum's own, or the byte an optional puts in front. */
FeIrType tag_type_of(const FeType *t)
{
if (!t) return FE_IR_I8;
if (t->kind == FE_TYPE_ERROR_UNION) return FE_IR_I16;
if (t->kind == FE_TYPE_ENUM) return t->bits > 8U ? FE_IR_I16 : FE_IR_I8;
return FE_IR_I8;
}
/* Give an arm's names somewhere to live and put the variant's payload there.
The payload is copied rather than pointed at: an arm that takes ownership of
what it matched is the normal case, and the checker has already decided
whether that was allowed. */
void bind_payload(Lower *L, Slot subject, const FeType *t,
const FeVariantType *v, FeNode *arm)
{
FeNode *name;
unsigned i;
long base;
if (!v || !v->field_count || !arm->children || !subject.is_place) return;
base = (long)fe_type_payload_offset(t);
name = arm->children;
for (i = 0; i < v->field_count && name; ++i, name = name->next) {
FeType *ft = v->fields[i].type;
unsigned local = declare_var(L, name->cname, ft, name->text);
FeIrPlace from = subject.place;
from.offset += base + (long)v->fields[i].offset;
store_into(L, fe_ir_at_local(local, 0),
slot_place(from, ir_type(ft), ir_size(ft)), name,
ir_size(ft));
}
}
/* `if let Some(x) = opt { .. } else { .. }` -- and its None twin.
The optional is read once into a place, the tag decides the branch, and the
binding gets what was inside. A binding whose type is a reference gets the
address instead of a copy: the checker chose that when the payload was not
something you may quietly duplicate. */
void lower_if_let(Lower *L, FeNode *n)
{
FeType *opt = n->a ? n->a->sem_type : 0;
Slot value = lower_expr(L, n->a);
FeNode *binding = n->children;
int is_some = n->aux_text && !strcmp(n->aux_text, "Some");
unsigned tag;
FeIrBlock *present;
FeIrBlock *absent;
FeIrBlock *join;
if (!value.is_place) { fail(L, "if let over a temporary", n); return; }
tag = wrapper_tag(L, value, opt, n);
present = new_block(L);
absent = new_block(L);
join = new_block(L);
fe_ir_br(L->b, tag, present->id, absent->id);
/* Which side runs the body depends on which pattern was written. */
L->b = is_some ? present : absent;
if (is_some && binding) {
FeType *bt = binding->sem_type;
Slot payload = wrapper_payload(L, value, opt);
unsigned local = declare_var(L, binding->cname, bt, binding->text);
if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW)) {
/* The binding is a reference either way, but for two different
reasons. When the payload is itself a single pointer (`^T`,
`&T`) the binding *is* that pointer, so it has to be read out.
When the payload is a value the binding points at where it sits
inside the wrapper, so the address is what is wanted. Taking the
address in the first case gives a pointer to the pointer, and
the program reads an address where it expects a value. */
FeType *pl = opt ? (opt->kind == FE_TYPE_ERROR_UNION
? opt->error_value : opt->elem) : 0;
unsigned p = pl && ir_type(pl) == FE_IR_PTR
? as_value(L, payload, n) : as_address(L, payload, n);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), p, FE_IR_PTR);
} else
store_into(L, fe_ir_at_local(local, 0), payload, n, ir_size(bt));
}
lower_stmt(L, n->b);
fe_ir_jmp(L->b, join->id);
L->b = is_some ? absent : present;
if (n->c) lower_stmt(L, n->c);
fe_ir_jmp(L->b, join->id);
L->b = join;
}
void lower_match(Lower *L, FeNode *n)
{
FeType *t = n->a ? n->a->sem_type : 0;
FeIrType it = ir_type(t);
Slot subject = lower_expr(L, n->a);
unsigned value;
FeIrBlock *join;
FeNode *arm;
/* A variant that carries something is memory: the tag comes first and the
payload after it. Reading the tag is then the same question either way,
just from a different place. */
if (it == FE_IR_MEM) {
if (!subject.is_place) { fail(L, "a match over a temporary", n); return; }
it = tag_type_of(t);
value = fe_ir_load(L->m, L->b, it, subject.place);
} else {
value = as_value(L, subject, n->a);
}
join = new_block(L);
for (arm = n->children; arm; arm = arm->next) {
FeIrBlock *body;
FeIrBlock *next;
unsigned want;
unsigned same;
FeVariantType *v;
if (arm->kind != FE_N_ARM) continue;
if (arm->text && !strcmp(arm->text, "_")) {
lower_stmt(L, arm->a);
fe_ir_jmp(L->b, join->id);
L->b = join;
return;
}
v = t && t->kind == FE_TYPE_ENUM && arm->text
? fe_type_variant(t, arm->text) : 0;
want = fe_ir_const(L->m, L->b, it,
v ? (long)v->tag : literal_value(arm));
same = fe_ir_binary(L->m, L->b, FE_IR_EQ, it, value, want, 1);
body = new_block(L);
next = new_block(L);
fe_ir_br(L->b, same, body->id, next->id);
L->b = body;
bind_payload(L, subject, t, v, arm);
lower_stmt(L, arm->a);
fe_ir_jmp(L->b, join->id);
L->b = next;
}
fe_ir_jmp(L->b, join->id);
L->b = join;
}
void lower_stmt(Lower *L, FeNode *n)
{
FeNode *x;
if (!n || L->failed) return;
switch (n->kind) {
case FE_N_BLOCK: {
unsigned outer = L->owed_count;
for (x = n->children; x; x = x->next) lower_stmt(L, x);
/* Leaving a block normally settles what it owes. An exit that jumped
away already settled on its way out. */
if (!L->b->terminated) run_deferred(L, outer);
L->owed_count = outer;
return;
}
case FE_N_LET:
case FE_N_VAR:
case FE_N_CONST: {
unsigned local = declare_var(L, n->cname, n->sem_type, n->text);
/* `undefined` says the storage starts out unset, so there is nothing
to write into it. */
if (n->b && n->b->kind == FE_N_LITERAL && n->b->text &&
!strcmp(n->b->text, "undefined")) return;
if (n->b) {
Slot v = lower_expr(L, n->b);
unsigned flag;
store_into(L, fe_ir_at_local(local, 0), v, n, ir_size(n->sem_type));
if (release_flag(L, local, &flag)) {
unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1);
fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), one, FE_IR_I8);
}
}
return;
}
case FE_N_ASSIGN: {
Slot dst = lower_expr(L, n->a);
Slot v = lower_expr(L, n->b);
if (!dst.is_place) { fail(L, "an assignment to a value", n); return; }
store_into(L, dst.place, v, n, dst.size);
return;
}
case FE_N_EXPR_STMT:
lower_expr(L, n->a);
return;
case FE_N_RETURN:
lower_return(L, n);
return;
case FE_N_IF:
if (n->text && !strcmp(n->text, "if let")) { lower_if_let(L, n); return; }
lower_if(L, n);
return;
case FE_N_WHILE:
lower_while(L, n);
return;
case FE_N_BREAK:
if (L->loop_depth) fe_ir_jmp(L->b, L->break_target[L->loop_depth - 1]);
return;
case FE_N_CONTINUE:
if (L->loop_depth)
fe_ir_jmp(L->b, L->continue_target[L->loop_depth - 1]);
return;
case FE_N_UNSAFE:
lower_stmt(L, n->a);
return;
case FE_N_DEFER:
if (lower_reserve(L, (void **)&L->owed, &L->owed_capacity,
L->owed_count, (unsigned long)sizeof *L->owed)) {
L->owed[L->owed_count].block = n->a;
L->owed[L->owed_count].local = 0;
L->owed[L->owed_count].flag = 0;
L->owed[L->owed_count].type = 0;
++L->owed_count;
}
return;
case FE_N_FOR:
lower_for(L, n);
return;
case FE_N_MATCH:
lower_match(L, n);
return;
default:
fail(L, "this statement", n);
return;
}
}
/* ------------------------------------------------------------ functions --- */
/* A global is static storage. SPEC 7.1: its initializer is evaluated at
compile time, so what reaches here is either a constant to place in the
image or nothing, and the storage starts as zeroes. */
void lower_global(Lower *L, FeNode *n)
{
FeType *t = n->sem_type;
unsigned char *init = 0;
unsigned long size = ir_size(t);
if (!n->cname) return;
/* A text constant is a pointer and a length. The pointer is not a number
anyone knows yet, so the bytes carry a hole and the linker fills it. */
if (n->b && n->b->kind == FE_N_LITERAL && n->b->text &&
n->b->text[0] == '"' && t &&
(t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR)) {
char text[1024];
unsigned long raw = strlen(n->b->text);
unsigned long len = 0;
unsigned long i;
const char *label;
FeIrGlobal *g;
if (raw >= 2) raw -= 2;
for (i = 0; i < raw && len + 1 < sizeof text; ++i) {
char ch = n->b->text[1 + i];
if (ch == 92 && i + 1 < raw) {
++i;
switch (n->b->text[1 + i]) {
case 'n': ch = 10; break;
case 't': ch = 9; break;
case 'r': ch = 13; break;
case '0': ch = 0; break;
default: ch = n->b->text[1 + i]; break;
}
}
text[len++] = ch;
}
label = fe_ir_string(L->m, text, len);
init = (unsigned char *)fe_arena_alloc(&L->m->arena, 8);
if (!init || !label) return;
for (i = 0; i < 8; ++i) init[i] = 0;
for (i = 0; i < 4; ++i) init[4 + i] = (unsigned char)((len >> (i * 8)) & 0xFF);
g = fe_ir_global(L->m, n->cname, FE_IR_MEM, 8, 4, init);
fe_ir_global_ref(L->m, g, (unsigned long)SLICE_PTR_OFFSET, label);
return;
}
if (n->b && n->b->kind == FE_N_LITERAL && size && size <= 8) {
long v = literal_value(n->b);
unsigned long i;
init = (unsigned char *)fe_arena_alloc(&L->m->arena, (size_t)size);
if (init)
for (i = 0; i < size; ++i)
init[i] = (unsigned char)((v >> (i * 8)) & 0xFF);
}
fe_ir_global(L->m, n->cname, ir_type(t), size, ir_align(t), init);
}
/* A declaration with type parameters is a pattern, not code. */
int struct_is_generic(const FeNode *decl)
{
return decl && decl->a && decl->a->children != 0;
}
int fn_is_generic(const FeNode *fn)
{
FeNode *p;
if (!fn) return 0;
for (p = fn->a ? fn->a->children : 0; p; p = p->next)
if (p->flags & FE_NODE_COMPTIME) return 1;
return 0;
}
void lower_fn_as(Lower *L, FeNode *fn, const char *name)
{
FeNode *p;
FeType *ret = fn->b ? fe_type_from_ast(&L->c->types, fn->b) : 0;
FeIrFunc *f;
if (!name) return;
f = fe_ir_func(L->m, name, ir_type(ret), ir_size(ret));
if (!f) return;
L->fn = f;
L->ret_type = ret;
L->var_count = 0;
L->loop_depth = 0;
/* A hidden first parameter holds where an aggregate result goes. */
if (f->returns_by_address)
L->ret_local = fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, "result");
for (p = fn->a ? fn->a->children : 0; p; p = p->next) {
FeType *pt;
int by_address;
unsigned local;
/* A comptime parameter was consumed at compile time; it has no
storage and takes no argument slot. */
if (p->flags & FE_NODE_COMPTIME) continue;
pt = fe_type_from_ast(&L->c->types, p->a);
/* An aggregate parameter arrives as an address. */
by_address = ir_type(pt) == FE_IR_MEM;
local = by_address
? fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, p->text)
: fe_ir_local(L->m, f, ir_type(pt), ir_size(pt), ir_align(pt),
p->text);
if (lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count, (unsigned long)sizeof(LowerVar))) {
L->vars[L->var_count].cname = p->cname;
L->vars[L->var_count].local = local;
L->vars[L->var_count].by_address = by_address;
++L->var_count;
}
}
f->param_count = f->local_count;
L->b = fe_ir_block(L->m, f);
lower_stmt(L, fn->c);
/* A void function may just run off the end. */
fe_ir_ret(L->b, 0, 0);
}
void lower_fn(Lower *L, FeNode *fn)
{
lower_fn_as(L, fn, fn->cname);
}
int fe_lower_program(FeCheck *c, FeIrModule *out)
{
Lower L;
unsigned u;
FeNode *n;
memset(&L, 0, sizeof L);
L.c = c;
L.m = out;
/* The codes have to be known while the bodies are lowered, so the names
are gathered from the whole build first. */
for (u = 0; u < c->build->count; ++u)
collect_error_names(&L, c->build->units[u].ast.root);
for (u = 0; u < c->build->count; ++u) {
FeUnit *unit = &c->build->units[u];
c->ast = &unit->ast;
c->unit = unit;
c->types.unit_name = unit->name[0] ? unit->name : "unit";
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next)
if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST)
lower_global(&L, n);
else if (n->kind == FE_N_STRUCT && !struct_is_generic(n)) {
/* A method is a function whose first parameter is the value it
was reached through; the storage is the same either way. */
FeNode *m;
for (m = n->children; m; m = m->next)
if (m->kind == FE_N_FN && m->c) lower_fn(&L, m);
}
else if (n->kind == FE_N_FN && !n->c) {
/* A declaration with no body is something the linker will
find: the runtime, or a C library. */
FeType *ret = n->b ? fe_type_from_ast(&c->types, n->b) : 0;
FeIrFunc *f;
if (!n->cname) continue;
f = fe_ir_func(out, n->cname, ir_type(ret), ir_size(ret));
if (f) f->is_extern = 1;
}
else if (n->kind == FE_N_FN && n->c && !fn_is_generic(n)) {
lower_fn(&L, n);
/* The entry unit is the one the build was rooted at. */
if (u == 0 && n->text && !strcmp(n->text, "main"))
out->entry_main = n->cname;
}
}
/* Each instance the checker reached is a function of its own: the same
body, read with different types bound, under its own link name. This is
where monomorphisation actually produces code -- the front end only
decided which instances exist. */
for (u = 0; u < c->instance_count && !L.failed; ++u) {
FeInstance *inst = &c->instances[u];
FeUnit *home;
FeTypeBind save[FE_TYPE_PARAM_MAX];
unsigned save_count;
unsigned k;
if (!inst->decl || !inst->decl->c || !inst->cname || !inst->home)
continue;
home = 0;
for (k = 0; k < c->build->count; ++k)
if (!strcmp(c->build->units[k].name, inst->home))
home = &c->build->units[k];
if (!home) continue;
c->ast = &home->ast;
c->unit = home;
c->types.unit_name = home->name;
save_count = c->types.param_count;
for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) save[k] = c->types.params[k];
c->types.param_count = inst->bind_count;
for (k = 0; k < inst->bind_count && k < FE_TYPE_PARAM_MAX; ++k)
c->types.params[k] = inst->binds[k];
lower_fn_as(&L, inst->decl, inst->cname);
c->types.param_count = save_count;
for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) c->types.params[k] = save[k];
}
return !L.failed;
}
+123
View File
@@ -0,0 +1,123 @@
#include "m7.h"
#include <stdio.h>
#include <string.h>
static char *m7_generated_name(FeTypeCtx *ctx, const char *prefix)
{
char number[24];
char *p;
unsigned long n;
sprintf(number, "%u", ctx->generated_serial++);
n = (unsigned long)strlen(prefix) + (unsigned long)strlen(number) + 1UL;
p = (char *)fe_arena_alloc(ctx->arena, n);
if (!p) return 0;
strcpy(p, prefix);
strcat(p, number);
return p;
}
int fe_m7_optional_uses_niche(const FeType *payload)
{
if (!payload) return 0;
if (payload->kind == FE_TYPE_REF) return 1;
if (payload->kind == FE_TYPE_OWNED &&
!(payload->elem && payload->elem->kind == FE_TYPE_SLICE))
return 1;
return 0;
}
FeType *fe_m7_optional_type(FeTypeCtx *ctx, FeType *payload)
{
char key[128];
FeType *t;
if (!ctx || !payload) return 0;
sprintf(key, "?%s", payload->name);
t = fe_type_intern(ctx, key);
if (!t) return 0;
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_OPTIONAL;
t->elem = payload;
t->unwrap_cname = m7_generated_name(ctx, "fe_unwrap_option_");
t->drop_cname = m7_generated_name(ctx, "fe_drop_option_");
if (!fe_m7_optional_uses_niche(payload)) {
t->cname = m7_generated_name(ctx, "struct fe_option_");
t->maker = m7_generated_name(ctx, "fe_make_option_");
t->none_cname = m7_generated_name(ctx, "fe_none_option_");
}
}
return t;
}
int fe_m7_can_contextual_null(const FeType *expected)
{
return expected && expected->kind == FE_TYPE_OPTIONAL;
}
FeType *fe_m7_error_union_type(FeTypeCtx *ctx, FeType *error_type,
FeType *value_type)
{
char key[160];
FeType *t;
if (!ctx || !value_type) return 0;
if (!error_type || strcmp(error_type->name, "core.Error") == 0)
return fe_type_error_union(ctx, value_type);
sprintf(key, "%s!%s", error_type->name, value_type->name);
t = fe_type_intern(ctx, key);
if (!t) return 0;
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_ERROR_UNION;
t->elem = error_type;
t->error_value = value_type;
t->drop_cname = m7_generated_name(ctx, "fe_drop_result_");
if (value_type->kind != FE_TYPE_VOID) {
t->cname = m7_generated_name(ctx, "struct fe_result_");
t->maker = m7_generated_name(ctx, "fe_make_result_");
t->none_cname = m7_generated_name(ctx, "fe_fail_result_");
t->alloc_cname = m7_generated_name(ctx, "fe_alloc_result_");
}
}
return t;
}
FeType *fe_m7_error_type(FeTypeCtx *ctx, const FeType *error_union)
{
if (!ctx || !error_union || error_union->kind != FE_TYPE_ERROR_UNION)
return 0;
if (error_union->elem) return error_union->elem;
return fe_type_intern(ctx, "core.Error");
}
FeM7ContextKind fe_m7_error_context(FeTypeCtx *ctx, const FeType *expected,
const FeType *actual)
{
FeType *error_type;
if (!ctx || !expected || !actual ||
expected->kind != FE_TYPE_ERROR_UNION)
return FE_M7_CONTEXT_NONE;
if (expected->error_value && fe_type_equal(expected->error_value, actual))
return FE_M7_CONTEXT_SUCCESS;
error_type = fe_m7_error_type(ctx, expected);
if (error_type && fe_type_equal(error_type, actual))
return FE_M7_CONTEXT_FAILURE;
return FE_M7_CONTEXT_NONE;
}
FeM7LazyKind fe_m7_lazy_kind(const FeNode *node)
{
if (!node || !node->text) return FE_M7_LAZY_NONE;
if (strcmp(node->text, "orelse") == 0) return FE_M7_LAZY_ORELSE;
if (strcmp(node->text, "catch") == 0) return FE_M7_LAZY_CATCH;
return FE_M7_LAZY_NONE;
}
int fe_m7_is_try(const FeNode *node)
{
return node && node->kind == FE_N_UNARY && node->text &&
strcmp(node->text, "try") == 0;
}
int fe_m7_is_null(const FeNode *node)
{
return node && node->kind == FE_N_LITERAL && node->text &&
strcmp(node->text, "null") == 0;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef FE_M7_H
#define FE_M7_H
#include "types.h"
typedef enum FeM7ContextKind {
FE_M7_CONTEXT_NONE = 0,
FE_M7_CONTEXT_SUCCESS,
FE_M7_CONTEXT_FAILURE
} FeM7ContextKind;
typedef enum FeM7LazyKind {
FE_M7_LAZY_NONE = 0,
FE_M7_LAZY_ORELSE,
FE_M7_LAZY_CATCH
} FeM7LazyKind;
FeType *fe_m7_optional_type(FeTypeCtx *ctx, FeType *payload);
int fe_m7_optional_uses_niche(const FeType *payload);
int fe_m7_can_contextual_null(const FeType *expected);
FeType *fe_m7_error_union_type(FeTypeCtx *ctx, FeType *error_type,
FeType *value_type);
FeType *fe_m7_error_type(FeTypeCtx *ctx, const FeType *error_union);
FeM7ContextKind fe_m7_error_context(FeTypeCtx *ctx, const FeType *expected,
const FeType *actual);
FeM7LazyKind fe_m7_lazy_kind(const FeNode *node);
int fe_m7_is_try(const FeNode *node);
int fe_m7_is_null(const FeNode *node);
#endif
+748
View File
@@ -0,0 +1,748 @@
#include "own.h"
#include <string.h>
static FeLoc fe_own_no_loc(void)
{
FeLoc loc;
loc.file = 0;
loc.line = 0;
loc.col = 0;
return loc;
}
static void fe_own_error_note(FeDiags *diags, FeLoc loc, const char *msg,
FeLoc note, const char *note_msg)
{
fe_diag_error(diags, loc, msg);
if (note.file) fe_diag_note_src(diags, note, note_msg);
}
int fe_own_is_copy_type(FeType *type)
{
unsigned i;
if (!type) return 1;
if (type->kind == FE_TYPE_OWNED) return 0;
if (type->kind == FE_TYPE_REF || type->kind == FE_TYPE_SLICE)
return !type->ref_mut;
if (type->kind == FE_TYPE_OPTIONAL)
return fe_own_is_copy_type(type->elem);
if (type->kind == FE_TYPE_ERROR_UNION)
return fe_own_is_copy_type(type->error_value);
if (type->kind == FE_TYPE_ARRAY)
return fe_own_is_copy_type(type->elem);
if (type->kind == FE_TYPE_STRUCT) {
if (type->has_drop) return 0;
for (i = 0; i < type->field_count; ++i)
if (!fe_own_is_copy_type(type->fields[i].type)) return 0;
}
if (type->kind == FE_TYPE_ENUM) {
for (i = 0; i < type->variant_count; ++i) {
unsigned j;
for (j = 0; j < type->variants[i].field_count; ++j)
if (!fe_own_is_copy_type(type->variants[i].fields[j].type))
return 0;
}
}
return 1;
}
int fe_own_is_reference_like(FeType *type)
{
return type && (type->kind == FE_TYPE_REF ||
type->kind == FE_TYPE_SLICE ||
type->kind == FE_TYPE_STR);
}
static FeNode *fe_own_root_expr(FeNode *expr)
{
if (!expr) return 0;
if (expr->kind == FE_N_IDENT) return expr;
if (expr->kind == FE_N_MEMBER || expr->kind == FE_N_INDEX)
return fe_own_root_expr(expr->a);
if (expr->kind == FE_N_UNARY && expr->text &&
(strcmp(expr->text, "&") == 0 || strcmp(expr->text, "&mut") == 0))
return fe_own_root_expr(expr->a);
return 0;
}
static int fe_own_expr_has_projection(FeNode *expr)
{
if (!expr) return 0;
if (expr->kind == FE_N_MEMBER || expr->kind == FE_N_INDEX) return 1;
if (expr->kind == FE_N_UNARY && expr->text &&
(strcmp(expr->text, "&") == 0 || strcmp(expr->text, "&mut") == 0))
return fe_own_expr_has_projection(expr->a);
return 0;
}
int fe_own_place_from_expr(FeNode *expr, FeOwnPlace *place)
{
FeNode *root;
if (!place) return 0;
place->root = 0;
place->root_cname = 0;
place->projected = 0;
root = fe_own_root_expr(expr);
if (!root) return 0;
place->root = root;
place->root_cname = root->cname ? root->cname : root->text;
place->projected = fe_own_expr_has_projection(expr);
return place->root_cname != 0;
}
void fe_own_state_init(FeOwnState *state, int initialized)
{
unsigned i;
if (!state) return;
state->move = FE_OWN_AVAILABLE;
state->initialized = initialized != 0;
state->shared = 0;
state->exclusive = 0;
state->borrow_conflict = 0;
state->move_loc = fe_own_no_loc();
state->borrow_loc = fe_own_no_loc();
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
state->fields[i].name = 0;
state->fields[i].shared = 0;
state->fields[i].exclusive = 0;
state->fields[i].loc = fe_own_no_loc();
}
}
static int fe_own_require_value(FeDiags *diags, FeOwnState *state, FeLoc loc)
{
if (state->move == FE_OWN_MOVED) {
fe_own_error_note(diags, loc, "use of moved value", state->move_loc,
"value was moved here");
return 0;
}
if (state->move == FE_OWN_MAYBE_MOVED) {
fe_diag_error(diags, loc, "use of possibly moved value");
return 0;
}
if (!state->initialized) {
fe_diag_error(diags, loc, "use of uninitialized variable");
return 0;
}
return 1;
}
static int fe_own_require_stable_borrow(FeDiags *diags, FeOwnState *state,
FeLoc loc)
{
if (!state->borrow_conflict) return 1;
fe_own_error_note(diags, loc,
"incompatible borrow state across control-flow paths",
state->borrow_loc, "borrow originated here");
return 0;
}
/* Whole-value state only: what a field access has to get past before it looks
at its own entry. `check` reports and decides; `apply` also records. */
static int fe_own_access_whole(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc);
static int fe_own_access_whole_check(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc);
/* The entry for this field, or null. `make` asks for one to be created. */
static FeOwnField *fe_own_field_slot(FeOwnState *state, const char *field,
int make)
{
unsigned i;
unsigned free_slot = FE_OWN_FIELD_MAX;
if (!state || !field) return 0;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
if (state->fields[i].name &&
strcmp(state->fields[i].name, field) == 0) return &state->fields[i];
if (!state->fields[i].name && free_slot == FE_OWN_FIELD_MAX)
free_slot = i;
}
if (!make || free_slot == FE_OWN_FIELD_MAX) return 0;
state->fields[free_slot].name = field;
state->fields[free_slot].shared = 0;
state->fields[free_slot].exclusive = 0;
state->fields[free_slot].loc = fe_own_no_loc();
return &state->fields[free_slot];
}
/* A live borrow of some field, for the accesses that reach the whole value. */
static const FeOwnField *fe_own_field_live(const FeOwnState *state,
int mut_only)
{
unsigned i;
if (!state) return 0;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
const FeOwnField *f = &state->fields[i];
if (!f->name) continue;
if (f->exclusive) return f;
if (!mut_only && f->shared) return f;
}
return 0;
}
void fe_own_release_shared_field(FeOwnState *state, const char *field)
{
FeOwnField *f = fe_own_field_slot(state, field, 0);
if (!f || !f->shared) { fe_own_release_shared(state); return; }
--f->shared;
if (!f->shared && !f->exclusive) f->name = 0;
}
void fe_own_release_exclusive_field(FeOwnState *state, const char *field)
{
FeOwnField *f = fe_own_field_slot(state, field, 0);
if (!f || !f->exclusive) { fe_own_release_exclusive(state); return; }
f->exclusive = 0;
if (!f->shared) f->name = 0;
}
int fe_own_access(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc)
{
return fe_own_access_field(diags, state, 0, access, loc);
}
int fe_own_access_field(FeDiags *diags, FeOwnState *state, const char *field,
FeOwnAccessKind access, FeLoc loc)
{
FeOwnField *f;
const FeOwnField *other;
if (!state) return 0;
if (access == FE_OWN_PROJECTION) return 1;
if (!field) {
/* Reaching the whole value: a borrow of any part of it is in the way.
A shared borrow of a field still lets the whole be read. */
other = fe_own_field_live(state, access == FE_OWN_READ);
if (other) {
fe_own_error_note(diags, loc,
access == FE_OWN_WRITE ? "cannot write while value is borrowed" :
access == FE_OWN_MOVE ? "cannot move while value is borrowed" :
access == FE_OWN_READ ?
"cannot read directly while value is mutably borrowed" :
"cannot borrow while a field of the value is borrowed",
other->loc, "borrow originated here");
return 0;
}
return fe_own_access_whole(diags, state, access, loc);
}
/* Reaching one field: a borrow of the whole value is in the way, and so is
a borrow of this same field. A borrow of a different field is not. */
if (!fe_own_access_whole_check(diags, state, access, loc)) return 0;
f = fe_own_field_slot(state, field,
access == FE_OWN_BORROW_SHARED ||
access == FE_OWN_BORROW_MUT);
if (!f) {
/* No room left in the table, so this borrow covers the whole value.
That reports more than it has to and never less. */
if (access == FE_OWN_BORROW_SHARED || access == FE_OWN_BORROW_MUT)
return fe_own_access_whole(diags, state, access, loc);
return 1;
}
switch (access) {
case FE_OWN_READ:
if (f->exclusive) {
fe_own_error_note(diags, loc,
"cannot read directly while value is mutably borrowed",
f->loc, "mutable borrow originated here");
return 0;
}
return 1;
case FE_OWN_WRITE:
case FE_OWN_MOVE:
if (f->shared || f->exclusive) {
fe_own_error_note(diags, loc,
access == FE_OWN_WRITE ? "cannot write while value is borrowed"
: "cannot move while value is borrowed",
f->loc, "borrow originated here");
return 0;
}
return 1;
case FE_OWN_BORROW_SHARED:
if (f->exclusive) {
fe_own_error_note(diags, loc,
"cannot create shared borrow while mutable borrow is live",
f->loc, "mutable borrow originated here");
return 0;
}
if (!f->shared) f->loc = loc;
++f->shared;
return 1;
case FE_OWN_BORROW_MUT:
if (f->shared || f->exclusive) {
fe_own_error_note(diags, loc,
"cannot create mutable borrow while another borrow is live",
f->loc, "existing borrow originated here");
return 0;
}
f->exclusive = 1;
f->loc = loc;
return 1;
default:
break;
}
return 1;
}
static int fe_own_access_whole_check(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc)
{
if (!fe_own_require_stable_borrow(diags, state, loc)) return 0;
if (access == FE_OWN_WRITE) {
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc, "cannot write while value is borrowed",
state->borrow_loc, "borrow originated here");
return 0;
}
return 1;
}
if (!fe_own_require_value(diags, state, loc)) return 0;
if (access == FE_OWN_READ || access == FE_OWN_BORROW_SHARED) {
if (state->exclusive) {
fe_own_error_note(diags, loc, access == FE_OWN_READ ?
"cannot read directly while value is mutably borrowed" :
"cannot create shared borrow while mutable borrow is live",
state->borrow_loc, "mutable borrow originated here");
return 0;
}
return 1;
}
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc, access == FE_OWN_MOVE ?
"cannot move while value is borrowed" :
"cannot create mutable borrow while another borrow is live",
state->borrow_loc, "existing borrow originated here");
return 0;
}
return 1;
}
static int fe_own_access_whole(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc)
{
if (!state) return 0;
if (access == FE_OWN_PROJECTION) return 1;
if (!fe_own_require_stable_borrow(diags, state, loc)) return 0;
if (access == FE_OWN_WRITE) {
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc, "cannot write while value is borrowed",
state->borrow_loc, "borrow originated here");
return 0;
}
state->move = FE_OWN_AVAILABLE;
state->initialized = 1;
state->move_loc = fe_own_no_loc();
return 1;
}
if (!fe_own_require_value(diags, state, loc)) return 0;
switch (access) {
case FE_OWN_READ:
if (state->exclusive) {
fe_own_error_note(diags, loc,
"cannot read directly while value is mutably borrowed",
state->borrow_loc, "mutable borrow originated here");
return 0;
}
return 1;
case FE_OWN_MOVE:
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc, "cannot move while value is borrowed",
state->borrow_loc, "borrow originated here");
return 0;
}
state->move = FE_OWN_MOVED;
state->initialized = 0;
state->move_loc = loc;
return 1;
case FE_OWN_BORROW_SHARED:
if (state->exclusive) {
fe_own_error_note(diags, loc,
"cannot create shared borrow while mutable borrow is live",
state->borrow_loc, "mutable borrow originated here");
return 0;
}
if (!state->shared) state->borrow_loc = loc;
++state->shared;
return 1;
case FE_OWN_BORROW_MUT:
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc,
"cannot create mutable borrow while another borrow is live",
state->borrow_loc, "existing borrow originated here");
return 0;
}
state->exclusive = 1;
state->borrow_loc = loc;
return 1;
default:
break;
}
return 1;
}
int fe_own_call_shared_view(FeDiags *diags, FeOwnState *state, FeLoc loc)
{
if (!state) return 0;
if (!fe_own_require_stable_borrow(diags, state, loc)) return 0;
if (!fe_own_require_value(diags, state, loc)) return 0;
if (!state->exclusive) {
fe_diag_error(diags, loc,
"read-only reborrow requires a live mutable borrow");
return 0;
}
return 1;
}
void fe_own_release_shared(FeOwnState *state)
{
if (!state || !state->shared) return;
--state->shared;
if (!state->shared && !state->exclusive)
state->borrow_loc = fe_own_no_loc();
}
void fe_own_release_exclusive(FeOwnState *state)
{
if (!state) return;
state->exclusive = 0;
if (!state->shared) state->borrow_loc = fe_own_no_loc();
}
/* Merging two paths through the code: a borrow that is live on either side is
live after, because the checker cannot know which side ran. */
static void fe_own_merge_fields(FeOwnState *out, const FeOwnState *left,
const FeOwnState *right)
{
unsigned i;
unsigned j;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) out->fields[i] = left->fields[i];
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
const FeOwnField *r = &right->fields[i];
if (!r->name) continue;
for (j = 0; j < FE_OWN_FIELD_MAX; ++j) {
if (out->fields[j].name &&
strcmp(out->fields[j].name, r->name) != 0) continue;
if (!out->fields[j].name) out->fields[j] = *r;
else {
if (r->shared > out->fields[j].shared)
out->fields[j].shared = r->shared;
if (r->exclusive && !out->fields[j].exclusive) {
out->fields[j].exclusive = 1;
out->fields[j].loc = r->loc;
}
}
break;
}
}
}
FeOwnState fe_own_merge_state(FeOwnState left, FeOwnState right)
{
FeOwnState out;
fe_own_merge_fields(&out, &left, &right);
out.move = left.move == right.move ? left.move :
fe_own_merge_move(left.move, right.move);
out.initialized = left.initialized && right.initialized;
out.shared = left.shared > right.shared ? left.shared : right.shared;
out.exclusive = left.exclusive || right.exclusive;
out.borrow_conflict = left.borrow_conflict || right.borrow_conflict ||
(out.shared != 0 && out.exclusive != 0);
out.move_loc = left.move != FE_OWN_AVAILABLE ? left.move_loc : right.move_loc;
if (left.shared || left.exclusive || left.borrow_conflict)
out.borrow_loc = left.borrow_loc;
else
out.borrow_loc = right.borrow_loc;
return out;
}
int fe_own_state_equal(const FeOwnState *left, const FeOwnState *right)
{
if (!left || !right) return 0;
{
unsigned i;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
const FeOwnField *a = &left->fields[i];
const FeOwnField *b = &right->fields[i];
if (!a->name != !b->name) return 0;
if (a->name && strcmp(a->name, b->name) != 0) return 0;
if (a->shared != b->shared || a->exclusive != b->exclusive)
return 0;
}
}
return left->move == right->move &&
left->initialized == right->initialized &&
left->shared == right->shared &&
left->exclusive == right->exclusive &&
left->borrow_conflict == right->borrow_conflict;
}
int fe_own_loop_merge_state(FeOwnState entry, FeOwnState backedge,
FeOwnState *merged)
{
if (!merged) return 0;
*merged = fe_own_merge_state(entry, backedge);
return fe_own_state_equal(&entry, merged);
}
FeOwnProvenance fe_own_provenance_static(void)
{
FeOwnProvenance p;
p.kind = FE_OWN_PROV_STATIC;
p.param_index = 0;
return p;
}
FeOwnProvenance fe_own_provenance_param(unsigned param_index)
{
FeOwnProvenance p;
p.kind = FE_OWN_PROV_PARAM;
p.param_index = param_index;
return p;
}
FeOwnProvenance fe_own_merge_provenance(FeOwnProvenance left,
FeOwnProvenance right)
{
FeOwnProvenance invalid;
invalid.kind = FE_OWN_PROV_INVALID;
invalid.param_index = 0;
if (left.kind == FE_OWN_PROV_INVALID || right.kind == FE_OWN_PROV_INVALID)
return invalid;
if (left.kind == FE_OWN_PROV_STATIC) return right;
if (right.kind == FE_OWN_PROV_STATIC) return left;
if (left.param_index == right.param_index) return left;
return invalid;
}
void fe_own_liveness_init(FeOwnLiveness *live, FeArena *arena)
{
if (!live) return;
live->arena = arena;
live->items = 0;
live->count = 0;
live->capacity = 0;
live->ordinal = 0;
}
static FeOwnLastUse *fe_own_live_find(FeOwnLiveness *live, const char *cname)
{
unsigned i;
if (!live || !cname) return 0;
for (i = 0; i < live->count; ++i)
if (live->items[i].cname == cname ||
strcmp(live->items[i].cname, cname) == 0)
return &live->items[i];
return 0;
}
static int fe_own_live_add(FeOwnLiveness *live, FeNode *decl)
{
FeOwnLastUse *items;
FeOwnLastUse *slot;
unsigned capacity;
const char *cname;
if (!live || !decl || !live->arena)
return 1;
cname = decl->cname ? decl->cname : decl->text;
if (!cname || fe_own_live_find(live, cname)) return 1;
if (live->count == live->capacity) {
capacity = live->capacity ? live->capacity * 2U : 8U;
items = (FeOwnLastUse *)fe_arena_alloc(live->arena,
capacity * sizeof(FeOwnLastUse));
if (!items) return 0;
if (live->items)
memcpy(items, live->items, live->count * sizeof(FeOwnLastUse));
live->items = items;
live->capacity = capacity;
}
slot = &live->items[live->count++];
slot->cname = cname;
slot->decl = decl;
slot->last_node = 0;
slot->last_ordinal = 0;
slot->defer_extended = 0;
return 1;
}
static unsigned long fe_own_node_weight(FeNode *node);
static unsigned long fe_own_list_weight(FeNode *node)
{
unsigned long count;
count = 0;
while (node) {
count += fe_own_node_weight(node);
node = node->next;
}
return count;
}
static unsigned long fe_own_node_weight(FeNode *node)
{
unsigned long count;
if (!node) return 0;
count = 1;
count += fe_own_node_weight(node->a);
count += fe_own_node_weight(node->b);
count += fe_own_node_weight(node->c);
count += fe_own_list_weight(node->children);
return count;
}
static int fe_own_live_visit(FeOwnLiveness *live, FeNode *node,
unsigned long block_end,
unsigned long defer_until);
static int fe_own_live_visit_list(FeOwnLiveness *live, FeNode *node,
unsigned long block_end,
unsigned long defer_until)
{
while (node) {
if (!fe_own_live_visit(live, node, block_end, defer_until)) return 0;
node = node->next;
}
return 1;
}
static int fe_own_live_visit(FeOwnLiveness *live, FeNode *node,
unsigned long block_end,
unsigned long defer_until)
{
FeOwnLastUse *slot;
unsigned long end;
unsigned long effective;
if (!node) return 1;
++live->ordinal;
if (node->kind == FE_N_IDENT) {
slot = fe_own_live_find(live, node->cname ? node->cname : node->text);
if (slot) {
effective = defer_until ? defer_until : live->ordinal;
if (effective >= slot->last_ordinal) {
slot->last_ordinal = effective;
slot->last_node = node;
if (defer_until) slot->defer_extended = 1;
}
}
return 1;
}
if (node->kind == FE_N_BLOCK) {
end = live->ordinal + fe_own_list_weight(node->children);
return fe_own_live_visit_list(live, node->children, end, defer_until);
}
if (node->kind == FE_N_DEFER) {
effective = defer_until ? defer_until : block_end;
return fe_own_live_visit(live, node->a, block_end, effective);
}
if (!fe_own_live_visit(live, node->a, block_end, defer_until)) return 0;
if (!fe_own_live_visit(live, node->b, block_end, defer_until)) return 0;
if (!fe_own_live_visit(live, node->c, block_end, defer_until)) return 0;
if (!fe_own_live_visit_list(live, node->children, block_end, defer_until))
return 0;
if (node->kind == FE_N_LET || node->kind == FE_N_VAR ||
node->kind == FE_N_CONST)
return fe_own_live_add(live, node);
return 1;
}
int fe_own_collect_last_uses(FeOwnLiveness *live, FeNode *fn)
{
FeNode *param;
if (!live || !fn || fn->kind != FE_N_FN) return 0;
live->items = 0;
live->count = 0;
live->capacity = 0;
live->ordinal = 0;
for (param = fn->a ? fn->a->children : 0; param; param = param->next)
if (!fe_own_live_add(live, param)) return 0;
return fe_own_live_visit(live, fn->c, 0, 0);
}
const FeOwnLastUse *fe_own_last_use(const FeOwnLiveness *live,
const char *cname)
{
unsigned i;
if (!live || !cname) return 0;
for (i = 0; i < live->count; ++i)
if (live->items[i].cname == cname ||
strcmp(live->items[i].cname, cname) == 0)
return &live->items[i];
return 0;
}
static int fe_own_replace_unwrap(FeNode *expr)
{
FeNode *call;
FeNode *member;
if (!expr || expr->kind != FE_N_MEMBER || !expr->text ||
strcmp(expr->text,".?") != 0)
return 0;
call=expr->a;
if (!call || call->kind != FE_N_CALL || !call->a ||
call->a->kind != FE_N_MEMBER)
return 0;
member=call->a;
return member->a && member->a->kind==FE_N_IDENT && member->a->text &&
strcmp(member->a->text,"mem")==0 && member->b && member->b->text &&
strcmp(member->b->text,"replace")==0;
}
void fe_own_mark_consumed(FeDiags *diags, int *state, FeNode *decl,
FeNode *expr, FeType *type, int in_defer)
{
if (!expr || !type || fe_own_is_copy_type(type)) return;
if (expr->kind == FE_N_INDEX && type->kind == FE_TYPE_SLICE &&
(expr->c || !expr->b))
return;
if ((expr->kind == FE_N_MEMBER || expr->kind == FE_N_INDEX) &&
!fe_own_replace_unwrap(expr)) {
fe_diag_error(diags, expr->loc,
"cannot move a non-Copy value out of a projection; use mem.replace");
return;
}
if (expr->kind != FE_N_IDENT || !state) return;
if (in_defer) {
if (decl) decl->flags |= FE_OWN_NODE_DEFER_CAPTURE;
return;
}
*state = FE_OWN_MOVED;
expr->flags |= FE_OWN_NODE_CONSUMED;
}
void fe_own_check_use(FeDiags *diags, int state, FeLoc loc)
{
if (state == FE_OWN_MOVED)
fe_diag_error(diags, loc, "use of moved value");
else if (state == FE_OWN_MAYBE_MOVED)
fe_diag_error(diags, loc, "use of possibly moved value");
}
int fe_own_merge_move(int left, int right)
{
if (left == FE_OWN_MOVED && right == FE_OWN_MOVED)
return FE_OWN_MOVED;
if (left != FE_OWN_AVAILABLE || right != FE_OWN_AVAILABLE)
return FE_OWN_MAYBE_MOVED;
return FE_OWN_AVAILABLE;
}
int fe_own_loop_entry(int before, int after)
{
if (before == after) return before;
return FE_OWN_MAYBE_MOVED;
}
int fe_own_loop_exit(int state, int after)
{
if (after != FE_OWN_AVAILABLE) return FE_OWN_MAYBE_MOVED;
return state;
}
+126
View File
@@ -0,0 +1,126 @@
#ifndef FE_OWN_H
#define FE_OWN_H
#include "types.h"
#include "diag.h"
#define FE_OWN_NODE_CONSUMED 0x100U
#define FE_OWN_NODE_DEFER_CAPTURE 0x200U
enum FeOwnMoveState {
FE_OWN_AVAILABLE = 0,
FE_OWN_MOVED = 1,
FE_OWN_MAYBE_MOVED = 2
};
typedef enum FeOwnAccessKind {
FE_OWN_READ,
FE_OWN_WRITE,
FE_OWN_MOVE,
FE_OWN_BORROW_SHARED,
FE_OWN_BORROW_MUT,
FE_OWN_PROJECTION
} FeOwnAccessKind;
typedef enum FeOwnProvenanceKind {
FE_OWN_PROV_INVALID,
FE_OWN_PROV_STATIC,
FE_OWN_PROV_PARAM
} FeOwnProvenanceKind;
typedef struct FeOwnPlace {
FeNode *root;
const char *root_cname;
int projected;
} FeOwnPlace;
/* How many distinct fields of one value can be borrowed at once. Past this
a borrow falls back to covering the whole value, which reports more than it
has to but never less. */
#define FE_OWN_FIELD_MAX 4
/* A borrow of one field rather than of the whole value. `self.bytes` and
`self.used_bytes` are different places, so borrowing one has to leave the
other readable -- otherwise a method cannot write through one field while
reading another, which is most of what a method does. */
typedef struct FeOwnField {
const char *name;
unsigned shared;
int exclusive;
FeLoc loc;
} FeOwnField;
typedef struct FeOwnState {
int move;
int initialized;
unsigned shared;
int exclusive;
int borrow_conflict;
FeLoc move_loc;
FeLoc borrow_loc;
/* Whole-value state is above; these cover one field each. A whole-value
borrow conflicts with every field, and a field borrow conflicts with
the whole value and with itself. */
FeOwnField fields[FE_OWN_FIELD_MAX];
} FeOwnState;
typedef struct FeOwnProvenance {
FeOwnProvenanceKind kind;
unsigned param_index;
} FeOwnProvenance;
typedef struct FeOwnLastUse {
const char *cname;
FeNode *decl;
FeNode *last_node;
unsigned long last_ordinal;
int defer_extended;
} FeOwnLastUse;
typedef struct FeOwnLiveness {
FeArena *arena;
FeOwnLastUse *items;
unsigned count;
unsigned capacity;
unsigned long ordinal;
} FeOwnLiveness;
int fe_own_is_copy_type(FeType *type);
int fe_own_is_reference_like(FeType *type);
int fe_own_place_from_expr(FeNode *expr, FeOwnPlace *place);
void fe_own_state_init(FeOwnState *state, int initialized);
int fe_own_access(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc);
/* The same, but reaching only one field of the value. A null `field` is the
whole value and behaves exactly as `fe_own_access`. */
int fe_own_access_field(FeDiags *diags, FeOwnState *state, const char *field,
FeOwnAccessKind access, FeLoc loc);
int fe_own_call_shared_view(FeDiags *diags, FeOwnState *state, FeLoc loc);
void fe_own_release_shared(FeOwnState *state);
void fe_own_release_exclusive(FeOwnState *state);
void fe_own_release_shared_field(FeOwnState *state, const char *field);
void fe_own_release_exclusive_field(FeOwnState *state, const char *field);
FeOwnState fe_own_merge_state(FeOwnState left, FeOwnState right);
int fe_own_state_equal(const FeOwnState *left, const FeOwnState *right);
int fe_own_loop_merge_state(FeOwnState entry, FeOwnState backedge,
FeOwnState *merged);
FeOwnProvenance fe_own_provenance_static(void);
FeOwnProvenance fe_own_provenance_param(unsigned param_index);
FeOwnProvenance fe_own_merge_provenance(FeOwnProvenance left,
FeOwnProvenance right);
void fe_own_liveness_init(FeOwnLiveness *live, FeArena *arena);
int fe_own_collect_last_uses(FeOwnLiveness *live, FeNode *fn);
const FeOwnLastUse *fe_own_last_use(const FeOwnLiveness *live,
const char *cname);
void fe_own_mark_consumed(FeDiags *diags, int *state, FeNode *decl,
FeNode *expr, FeType *type, int in_defer);
void fe_own_check_use(FeDiags *diags, int state, FeLoc loc);
int fe_own_merge_move(int left, int right);
int fe_own_loop_entry(int before, int after);
int fe_own_loop_exit(int state, int after);
#endif
+394
View File
@@ -0,0 +1,394 @@
#include "parser.h"
#include <string.h>
#include <stdio.h>
static FeToken next(FeParser *p) { p->previous=p->current; p->current=fe_lexer_next(&p->lexer); return p->current; }
static int is(FeParser *p, FeTokKind k) { return p->current.kind==k; }
static int eat(FeParser *p, FeTokKind k) { if(is(p,k)){next(p);return 1;}return 0; }
static FeNode *toknode(FeParser *p, FeNodeKind k, FeToken t) { return fe_node(p->ast,k,t.loc,t.begin,t.length); }
static void error(FeParser *p, const char *s) { fe_diag_error(p->diags,p->current.loc,s); }
static int want(FeParser *p, FeTokKind k, const char *what)
{ if(eat(p,k)) return 1; error(p,what); return 0; }
static int is_name(FeParser *p) { return is(p,FE_TOK_IDENT)||is(p,FE_TOK_SELF)||is(p,FE_TOK_SELFTYPE); }
static FeNode *expr(FeParser *p, int minprec);
static FeNode *delimited_expr(FeParser *p);
static FeNode *type(FeParser *p);
static FeNode *statement(FeParser *p);
static FeNode *block(FeParser *p);
void fe_parser_init(FeParser *p, FeAst *ast, const char *src, unsigned long length, const char *file, FeDiags *d)
{
p->ast=ast; p->diags=d; p->forbid_struct_literal=0; fe_lexer_init(&p->lexer,src,length,file,d);
p->previous=p->current=fe_lexer_next(&p->lexer);
}
static void recover(FeParser *p)
{
while(!is(p,FE_TOK_EOF) && !is(p,FE_TOK_SEMI) && !is(p,FE_TOK_RBRACE)) next(p);
if(is(p,FE_TOK_SEMI)) next(p);
}
static FeNode *type_prefix(FeParser *p, FeTokKind k, FeToken op)
{
FeNode *n;
(void)k;
n=toknode(p,FE_N_TYPE,op); n->a=type(p); return n;
}
static FeNode *type(FeParser *p)
{
FeToken t=p->current; FeNode *n;
if (is(p,FE_TOK_QUESTION)||is(p,FE_TOK_BANG)||is(p,FE_TOK_STAR)||is(p,FE_TOK_XOR)) {
next(p); return type_prefix(p,t.kind,t);
}
if (is(p,FE_TOK_AND)) {
next(p); n=toknode(p,FE_N_TYPE,t); if(eat(p,FE_TOK_MUT)) n->text=fe_arena_strdup(&p->ast->arena,"&mut",4); n->a=type(p); return n;
}
if (is(p,FE_TOK_LBRACKET)) {
next(p); n=toknode(p,FE_N_TYPE,t);
if(!eat(p,FE_TOK_RBRACKET)) { n->a=expr(p,0); want(p,FE_TOK_RBRACKET,"expected ']' in array type"); }
else if(eat(p,FE_TOK_MUT)) n->text=fe_arena_strdup(&p->ast->arena,"[]mut",5);
n->b=type(p); return n;
}
if (is(p,FE_TOK_FN)) {
next(p); n=toknode(p,FE_N_TYPE,t); want(p,FE_TOK_LPAREN,"expected '(' in function type");
while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)) { fe_node_add(n,type(p)); if(!eat(p,FE_TOK_COMMA)) break; }
want(p,FE_TOK_RPAREN,"expected ')' in function type"); if(eat(p,FE_TOK_ARROW)) n->a=type(p); return n;
}
if (is_name(p) || is(p,FE_TOK_TYPE)) {
next(p); n=toknode(p,FE_N_TYPE,t);
if (eat(p,FE_TOK_DOT)) {
if(is_name(p)) {
FeToken mt=p->current;
FeNode *m=toknode(p,FE_N_IDENT,mt);
n->a=m;
next(p);
} else error(p,"expected type name after '.'");
}
if (eat(p,FE_TOK_BANG)) { FeNode *e=toknode(p,FE_N_TYPE,p->previous); e->a=n; e->b=type(p); return e; }
if (eat(p,FE_TOK_LPAREN)) { while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n,type(p));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' in generic type"); }
return n;
}
error(p,"expected type"); next(p); return fe_node(p->ast,FE_N_TYPE,t.loc,"error",5);
}
/* Is this binary node one of the six comparisons? They share a precedence
level and SPEC 6.2 forbids chaining them. The operator is the node's text. */
static int is_comparison(const char *op)
{
if(!op) return 0;
return !strcmp(op,"==")||!strcmp(op,"!=")||!strcmp(op,"<")||
!strcmp(op,"<=")||!strcmp(op,">")||!strcmp(op,">=");
}
static int precedence(FeTokKind k)
{
switch(k) {
case FE_TOK_ORELSE: case FE_TOK_CATCH:return 1;
case FE_TOK_OR_KW:return 2; case FE_TOK_AND_KW:return 3;
case FE_TOK_EQEQ: case FE_TOK_NE: case FE_TOK_LT: case FE_TOK_LE: case FE_TOK_GT: case FE_TOK_GE:return 4;
case FE_TOK_OR:return 5; case FE_TOK_XOR:return 6; case FE_TOK_AND:return 7;
case FE_TOK_SHL: case FE_TOK_SHR:return 8;
case FE_TOK_PLUS: case FE_TOK_MINUS: case FE_TOK_PLUS_WRAP: case FE_TOK_MINUS_WRAP:return 9;
case FE_TOK_STAR: case FE_TOK_SLASH: case FE_TOK_PERCENT: case FE_TOK_STAR_WRAP:return 10;
default:return 0;
}
}
static FeNode *primary(FeParser *p)
{
FeToken t=p->current; FeNode *n;
if (is(p,FE_TOK_LBRACKET)) {
FeNode *a=toknode(p,FE_N_ARRAY_INIT,t); next(p);
while(!is(p,FE_TOK_RBRACKET)&&!is(p,FE_TOK_EOF)) {
fe_node_add(a,delimited_expr(p));
if(!eat(p,FE_TOK_COMMA)) break;
}
want(p,FE_TOK_RBRACKET,"expected ']' after array literal");
return a;
}
if(is(p,FE_TOK_INT)||is(p,FE_TOK_CHAR)||is(p,FE_TOK_STRING)||is(p,FE_TOK_TRUE)||is(p,FE_TOK_FALSE)||is(p,FE_TOK_NULL)||is(p,FE_TOK_UNDEFINED)) {next(p);return toknode(p,FE_N_LITERAL,t);}
if(is_name(p) || is(p,FE_TOK_ERROR_KW)) {
next(p); n=toknode(p,FE_N_IDENT,t);
if(is(p,FE_TOK_LBRACE) && !p->forbid_struct_literal) {
FeNode *s=toknode(p,FE_N_STRUCT_INIT,t); next(p);
while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) { FeNode *f;
if(!is_name(p)){error(p,"expected field name");recover(p);break;} f=toknode(p,FE_N_FIELD,p->current);next(p);want(p,FE_TOK_COLON,"expected ':' after field");f->a=expr(p,0);fe_node_add(s,f);if(!eat(p,FE_TOK_COMMA))break;
} want(p,FE_TOK_RBRACE,"expected '}' in struct literal"); return s;
}
return n;
}
if(eat(p,FE_TOK_LPAREN)) { int old=p->forbid_struct_literal; p->forbid_struct_literal=0; n=expr(p,0); p->forbid_struct_literal=old; want(p,FE_TOK_RPAREN,"expected ')'"); if(n) n->flags|=FE_NODE_PAREN; return n; }
if(eat(p,FE_TOK_AT)) {
FeToken name=p->current; if(!is_name(p)){error(p,"expected builtin name after '@'");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"builtin",7);} next(p);
n=toknode(p,FE_N_CALL,name); n->text=fe_arena_strdup(&p->ast->arena,name.begin-1,name.length+1);
if(eat(p,FE_TOK_LPAREN)){while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n,delimited_expr(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after builtin");}
return n;
}
error(p,"expected expression"); next(p); return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"expression",10);
}
static FeNode *postfix(FeParser *p)
{
FeNode *n=primary(p);
for(;;) {
FeToken t=p->current; FeNode *m;
if(eat(p,FE_TOK_LPAREN)) { m=toknode(p,FE_N_CALL,t); m->a=n; while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(m,delimited_expr(p));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' after call"); n=m;
/* `Name(args){...}` builds an instance of a generic struct. Without
this the arguments have nowhere to go and only `Self{...}` or a
constructor can name one. Same `{` ambiguity as `Name{...}`
above, and the same guard settles it. */
if(is(p,FE_TOK_LBRACE) && !p->forbid_struct_literal) {
FeNode *s=toknode(p,FE_N_STRUCT_INIT,t); s->a=n; next(p);
while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) { FeNode *f;
if(!is_name(p)){error(p,"expected field name");recover(p);break;}
f=toknode(p,FE_N_FIELD,p->current);next(p);
want(p,FE_TOK_COLON,"expected ':' after field");
f->a=expr(p,0);fe_node_add(s,f);
if(!eat(p,FE_TOK_COMMA))break;
}
want(p,FE_TOK_RBRACE,"expected '}' in struct literal"); n=s;
} }
else if(eat(p,FE_TOK_LBRACKET)) {
m=toknode(p,FE_N_INDEX,t);m->a=n;
if(is(p,FE_TOK_DOTDOT)) { m->b=0; m->flags|=FE_NODE_SLICE; } else m->b=delimited_expr(p);
if(eat(p,FE_TOK_DOTDOT)) { m->flags|=FE_NODE_SLICE; if(!is(p,FE_TOK_RBRACKET)) m->c=delimited_expr(p); }
want(p,FE_TOK_RBRACKET,"expected ']' after index");n=m;
}
else if(eat(p,FE_TOK_DOT)) {
m=toknode(p,FE_N_MEMBER,t);m->a=n;
if(is_name(p)){m->b=toknode(p,FE_N_IDENT,p->current);next(p);}
else if(eat(p,FE_TOK_QUESTION)){m->text=fe_arena_strdup(&p->ast->arena,".?",2);}
else if(eat(p,FE_TOK_XOR)){m->text=fe_arena_strdup(&p->ast->arena,".^",2);m->b=fe_node(p->ast,FE_N_IDENT,p->previous.loc,"^",1);}
else error(p,"expected member name");
n=m;
if(is(p,FE_TOK_LBRACE) && !p->forbid_struct_literal) {
FeNode *s=toknode(p,FE_N_STRUCT_INIT,t); s->a=n; next(p);
while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) { FeNode *f;
if(!is_name(p)){error(p,"expected variant field");recover(p);break;}
f=toknode(p,FE_N_FIELD,p->current);next(p);want(p,FE_TOK_COLON,"expected ':' after variant field");f->a=expr(p,0);fe_node_add(s,f);if(!eat(p,FE_TOK_COMMA))break;
}
want(p,FE_TOK_RBRACE,"expected '}' in variant constructor");n=s;
}
}
else if(eat(p,FE_TOK_AS)) { m=toknode(p,FE_N_TYPE,t);m->a=n;m->b=type(p);n=m; }
else break;
}
return n;
}
static FeNode *expr(FeParser *p, int minprec)
{
FeToken t=p->current; FeNode *left,*n; int prec;
if(is(p,FE_TOK_MINUS)||is(p,FE_TOK_NOT)||is(p,FE_TOK_TILDE)||is(p,FE_TOK_XOR)||is(p,FE_TOK_AND)||is(p,FE_TOK_STAR)||is(p,FE_TOK_TRY)) { next(p); n=toknode(p,FE_N_UNARY,t); if(t.kind==FE_TOK_AND && eat(p,FE_TOK_MUT)) n->text=fe_arena_strdup(&p->ast->arena,"&mut",4); n->a=expr(p,11);
if(n->a && n->a->kind==FE_N_TYPE && n->a->b &&
!(n->a->flags & FE_NODE_PAREN))
error(p,"parenthesise: '-x as T' is read as -(x as T)");
left=n; }
else left=postfix(p);
for(;;) { t=p->current;prec=precedence(t.kind);if(prec<=minprec)break;
if(prec==4 && left && left->kind==FE_N_BINARY &&
!(left->flags & FE_NODE_PAREN) && is_comparison(left->text))
error(p,"comparisons do not chain; write 'a < b and b < c'");
next(p);n=toknode(p,FE_N_BINARY,t);n->a=left;if(t.kind==FE_TOK_CATCH && eat(p,FE_TOK_OR)){if(is_name(p))n->b=toknode(p,FE_N_IDENT,p->current),next(p);else error(p,"expected catch binding");want(p,FE_TOK_OR,"expected '|' after catch binding");n->c=block(p);}else n->b=expr(p,prec);left=n; }
return left;
}
static FeNode *header_expr(FeParser *p)
{
FeNode *n;
int old=p->forbid_struct_literal;
p->forbid_struct_literal=1;
n=expr(p,0);
p->forbid_struct_literal=old;
return n;
}
static FeNode *delimited_expr(FeParser *p)
{
FeNode *n;
int old=p->forbid_struct_literal;
p->forbid_struct_literal=0;
n=expr(p,0);
p->forbid_struct_literal=old;
return n;
}
static FeNode *params(FeParser *p)
{
FeNode *list=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"params",6);
want(p,FE_TOK_LPAREN,"expected '(' after function name");
while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)) { FeToken t=p->current; FeNode *q; int ct=0;
if(eat(p,FE_TOK_COMPTIME)) { t=p->previous; ct=1; }
if(!is_name(p)){error(p,"expected parameter name");recover(p);break;} q=toknode(p,FE_N_PARAM,p->current);if(ct){q->flags|=FE_NODE_COMPTIME;q->loc=t.loc;}next(p);want(p,FE_TOK_COLON,"expected ':' in parameter");q->a=type(p);fe_node_add(list,q);if(!eat(p,FE_TOK_COMMA))break;
}
want(p,FE_TOK_RPAREN,"expected ')' after parameters"); return list;
}
static FeNode *fn_decl(FeParser *p, int pub, int external, int interrupt, int interrupt_safe)
{
FeToken t=p->current, name; FeNode *n;
(void)interrupt; (void)interrupt_safe;
want(p,FE_TOK_FN,"expected 'fn'"); if(!is_name(p)){error(p,"expected function name");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"fn",2);}
name=p->current; n=toknode(p,FE_N_FN,t); if(pub) n->flags|=FE_NODE_PUB; if(external) n->flags|=FE_NODE_EXTERN; n->text=fe_arena_strdup(&p->ast->arena,name.begin,name.length); next(p); n->a=params(p); if(eat(p,FE_TOK_ARROW)) n->b=type(p);
if(eat(p,FE_TOK_SEMI)) {
/* A body-less function is a promise that someone else defines it, and
`extern` is how that promise is made. Without it the name is
mangled into this unit and nothing anywhere defines it. */
if(!external) error(p,"a function without a body must be extern");
return n;
}
if(external) error(p,"an extern function has no body");
n->c=block(p); return n;
}
static FeNode *field(FeParser *p, int pub)
{
FeToken t=p->current; FeNode *n;
if(!is_name(p)){error(p,"expected field name");recover(p);return 0;} next(p);n=toknode(p,FE_N_FIELD,t);if(pub)n->flags|=FE_NODE_PUB;want(p,FE_TOK_COLON,"expected ':' after field");n->a=type(p);if(!eat(p,FE_TOK_COMMA) && !is(p,FE_TOK_RBRACE)) error(p,"expected ',' after field");return n;
}
static FeNode *decl(FeParser *p)
{
int pub=0, external=0, interrupt=0, interrupt_safe=0, shared=0, atomic=0; FeToken t=p->current; FeNode *n; FeTokKind before;
(void)shared; (void)atomic;
if(eat(p,FE_TOK_PUB)) pub=1;
if(eat(p,FE_TOK_EXTERN)) {
external=1;
if(!is(p,FE_TOK_STRING)) error(p,"extern requires an ABI string");
else {
/* The token keeps its quotes, so "c" is four characters. */
FeToken abi=p->current;
if(abi.length!=3 || abi.begin[1]!='c')
error(p,"the only ABI is \"c\"");
next(p);
}
}
if(eat(p,FE_TOK_INTERRUPT)) interrupt=1;
if(eat(p,FE_TOK_INTERRUPT_SAFE)) interrupt_safe=1;
if(!is(p,FE_TOK_PACKED)) t=p->current;
if(is(p,FE_TOK_FN)) return fn_decl(p,pub,external,interrupt,interrupt_safe);
if(eat(p,FE_TOK_PACKED)) t=p->previous;
if(eat(p,FE_TOK_STRUCT)) { n=toknode(p,FE_N_STRUCT,t);if(pub)n->flags|=FE_NODE_PUB;if(t.kind==FE_TOK_PACKED)n->flags|=FE_NODE_PACKED;if(!is_name(p)){error(p,"expected struct name");return n;}next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);if(eat(p,FE_TOK_LPAREN)){n->a=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"generics",8);while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n->a,type(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after generic parameters");}want(p,FE_TOK_LBRACE,"expected '{' in struct");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){int mpub=eat(p,FE_TOK_PUB);if(is(p,FE_TOK_FN))fe_node_add(n,fn_decl(p,mpub,0,0,0));else fe_node_add(n,field(p,mpub));}want(p,FE_TOK_RBRACE,"expected '}' after struct");return n; }
if(eat(p,FE_TOK_ENUM)) { n=toknode(p,FE_N_ENUM,t);if(pub)n->flags|=FE_NODE_PUB;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected enum name");if(eat(p,FE_TOK_LPAREN)){n->a=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"generics",8);while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n->a,type(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after generic parameters");}want(p,FE_TOK_LBRACE,"expected '{' in enum");if(is(p,FE_TOK_RBRACE))error(p,"an enum needs at least one variant");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected variant name");recover(p);break;}if(eat(p,FE_TOK_LPAREN)){v->a=type(p);want(p,FE_TOK_RPAREN,"expected ')' in variant");}else if(eat(p,FE_TOK_LBRACE)){while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))fe_node_add(v,field(p,1));want(p,FE_TOK_RBRACE,"expected '}' in variant");}fe_node_add(n,v);if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RBRACE,"expected '}' after enum");return n; }
if(eat(p,FE_TOK_ERROR_KW)) { n=toknode(p,FE_N_ERROR_DECL,t);if(pub)n->flags|=FE_NODE_PUB;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected error name");want(p,FE_TOK_LBRACE,"expected '{' in error declaration");if(is(p,FE_TOK_RBRACE))error(p,"an error declaration needs at least one member");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected error member");recover(p);break;}want(p,FE_TOK_EQ,"expected '=' in error member");if(is(p,FE_TOK_INT)){v->a=toknode(p,FE_N_LITERAL,p->current);next(p);}else error(p,"an error code must be an integer literal");if(!is(p,FE_TOK_COMMA)&&!is(p,FE_TOK_RBRACE)){error(p,"an error code must be an integer literal");recover(p);break;}want(p,FE_TOK_COMMA,"expected ',' in error declaration");fe_node_add(n,v);}want(p,FE_TOK_RBRACE,"expected '}' after error");return n; }
if(eat(p,FE_TOK_SHARED)) { shared=1; if(eat(p,FE_TOK_ATOMIC)) atomic=1; if(!is(p,FE_TOK_VAR)) error(p,"expected 'var' after shared"); }
if(is(p,FE_TOK_CONST)||is(p,FE_TOK_STATIC)||is(p,FE_TOK_VAR)) { FeTokKind kk=p->current.kind;next(p);n=toknode(p,kk==FE_TOK_CONST?FE_N_CONST:FE_N_GLOBAL,t);if(pub)n->flags|=FE_NODE_PUB;if(kk==FE_TOK_STATIC)n->flags|=FE_NODE_STATIC;if(shared)n->flags|=FE_NODE_SHARED;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected declaration name");if(eat(p,FE_TOK_COLON))n->a=type(p);else if(kk!=FE_TOK_CONST)error(p,"a global declaration requires an explicit type");want(p,FE_TOK_EQ,"expected '=' in declaration");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after declaration");return n; }
error(p,"expected declaration"); before=p->current.kind; recover(p);
if (p->current.kind==before && p->current.kind!=FE_TOK_EOF) next(p);
return 0;
}
static FeNode *block(FeParser *p)
{
FeToken t=p->current; FeNode *n=toknode(p,FE_N_BLOCK,t);want(p,FE_TOK_LBRACE,"expected '{'");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *s=statement(p);if(s)fe_node_add(n,s);}want(p,FE_TOK_RBRACE,"expected '}'");return n;
}
static FeNode *statement(FeParser *p)
{
FeToken t=p->current; FeNode *n,*e;
if(is(p,FE_TOK_LBRACE)) return block(p);
if(eat(p,FE_TOK_LET)) { n=toknode(p,FE_N_LET,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected variable name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in let");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; }
if(eat(p,FE_TOK_VAR)) { n=toknode(p,FE_N_VAR,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected variable name");if(eat(p,FE_TOK_COLON))n->a=type(p);if(eat(p,FE_TOK_EQ))n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; }
if(eat(p,FE_TOK_CONST)) { n=toknode(p,FE_N_CONST,t);if(is_name(p)){n->text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);next(p);}else error(p,"expected constant name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in const");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; }
if(eat(p,FE_TOK_IF)) {
n=toknode(p,FE_N_IF,t);
if(eat(p,FE_TOK_LET)) {
FeToken pt=p->current;
n->text=fe_arena_strdup(&p->ast->arena,"if let",6);
if(is_name(p)) {
n->aux_text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);
next(p);
} else error(p,"expected if let pattern");
if(eat(p,FE_TOK_LPAREN)) {
if(is_name(p)) {
FeNode *binding=toknode(p,FE_N_IDENT,p->current);
next(p);
fe_node_add(n,binding);
} else error(p,"expected if let binding");
want(p,FE_TOK_RPAREN,"expected ')' in if let pattern");
}
want(p,FE_TOK_EQ,"expected '=' in if let");
(void)pt;
}
n->a=header_expr(p);n->b=block(p);if(eat(p,FE_TOK_ELSE))n->c=is(p,FE_TOK_IF)?statement(p):block(p);return n;
}
if(eat(p,FE_TOK_COMPTIME)) { n=toknode(p,FE_N_IF,t);want(p,FE_TOK_IF,"expected 'if' after comptime");n->text=fe_arena_strdup(&p->ast->arena,"comptime if",11);n->a=header_expr(p);n->b=block(p);if(eat(p,FE_TOK_ELSE))n->c=is(p,FE_TOK_IF)?statement(p):block(p);return n; }
if(eat(p,FE_TOK_WHILE)) {n=toknode(p,FE_N_WHILE,t);n->a=header_expr(p);n->b=block(p);return n;}
if(eat(p,FE_TOK_FOR)) {n=toknode(p,FE_N_FOR,t);if(is_name(p)){n->text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);next(p);}else error(p,"expected loop variable");if(eat(p,FE_TOK_COMMA)){if(is_name(p)){n->aux_text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);next(p);}else error(p,"expected second loop variable");}want(p,FE_TOK_IN,"expected 'in' in for");n->a=header_expr(p);if(eat(p,FE_TOK_DOTDOT))n->c=header_expr(p);n->b=block(p);return n;}
if(eat(p,FE_TOK_MATCH)) {
int old=p->forbid_struct_literal;
n=toknode(p,FE_N_MATCH,t); p->forbid_struct_literal=1; n->a=header_expr(p); p->forbid_struct_literal=old;
want(p,FE_TOK_LBRACE,"expected '{' after match expression");
while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) {
FeNode *arm=toknode(p,FE_N_ARM,p->current);
FeToken pt=p->current;
if(is_name(p)||is(p,FE_TOK_INT)||is(p,FE_TOK_CHAR)||is(p,FE_TOK_NULL)||is(p,FE_TOK_TRUE)||is(p,FE_TOK_FALSE)) {
arm->text=fe_arena_strdup(&p->ast->arena,pt.begin,pt.length); next(p);
} else { error(p,"expected match pattern"); recover(p); continue; }
if(eat(p,FE_TOK_LPAREN)) {
while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)) {
if(is_name(p)) { fe_node_add(arm,toknode(p,FE_N_IDENT,p->current)); next(p); }
else { error(p,"expected pattern binding"); recover(p); break; }
if(!eat(p,FE_TOK_COMMA)) break;
}
want(p,FE_TOK_RPAREN,"expected ')' after match pattern");
} else if(eat(p,FE_TOK_LBRACE)) {
while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) {
if(is_name(p)) { fe_node_add(arm,toknode(p,FE_N_IDENT,p->current)); next(p); }
else { error(p,"expected field binding"); recover(p); break; }
if(!eat(p,FE_TOK_COMMA)) break;
}
want(p,FE_TOK_RBRACE,"expected '}' after match pattern");
}
want(p,FE_TOK_FATARROW,"expected '=>' in match arm");
if(is(p,FE_TOK_LBRACE)) arm->a=block(p);
else { arm->a=expr(p,0); want(p,FE_TOK_SEMI,"expected ';' in match arm"); }
fe_node_add(n,arm);
}
want(p,FE_TOK_RBRACE,"expected '}' after match"); return n;
}
if(eat(p,FE_TOK_RETURN)) {n=toknode(p,FE_N_RETURN,t);if(!is(p,FE_TOK_SEMI))n->a=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after return");return n;}
if(eat(p,FE_TOK_BREAK)){n=toknode(p,FE_N_BREAK,t);want(p,FE_TOK_SEMI,"expected ';'");return n;}
if(eat(p,FE_TOK_CONTINUE)){n=toknode(p,FE_N_CONTINUE,t);want(p,FE_TOK_SEMI,"expected ';'");return n;}
if(eat(p,FE_TOK_DEFER)){n=toknode(p,FE_N_DEFER,t);n->a=block(p);return n;}
if(eat(p,FE_TOK_UNSAFE)){n=toknode(p,FE_N_UNSAFE,t);n->a=block(p);return n;}
if(eat(p,FE_TOK_CRITICAL)){n=toknode(p,FE_N_UNSAFE,t);n->text=fe_arena_strdup(&p->ast->arena,"critical",8);n->a=block(p);return n;}
if(eat(p,FE_TOK_ASM)){n=toknode(p,FE_N_ASM,t);want(p,FE_TOK_LBRACE,"expected '{' after asm");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))next(p);want(p,FE_TOK_RBRACE,"expected '}' after asm");return n;}
e=expr(p,0); if(is(p,FE_TOK_EQ)||is(p,FE_TOK_PLUS_EQ)||is(p,FE_TOK_MINUS_EQ)||is(p,FE_TOK_STAR_EQ)||is(p,FE_TOK_SLASH_EQ)||is(p,FE_TOK_PERCENT_EQ)||is(p,FE_TOK_AND_EQ)||is(p,FE_TOK_OR_EQ)||is(p,FE_TOK_XOR_EQ)||is(p,FE_TOK_SHL_EQ)||is(p,FE_TOK_SHR_EQ)){n=toknode(p,FE_N_ASSIGN,p->current);n->a=e;next(p);n->b=expr(p,0);}else{n=toknode(p,FE_N_EXPR_STMT,t);n->a=e;}want(p,FE_TOK_SEMI,"expected ';' after statement");return n;
}
/* A unit path is dotted: `game.world.map`. It is stored canonically, dots and
all, because that spelling is the unit's identity everywhere else. */
static char *unit_path(FeParser *p)
{
char buf[256];
unsigned long len=0;
if(!is_name(p)) return 0;
for(;;) {
unsigned long n=p->current.length;
if(len && len+1<sizeof buf) buf[len++]='.';
if(len+n>=sizeof buf){error(p,"unit path is too long");return 0;}
memcpy(buf+len,p->current.begin,n);
len+=n;
next(p);
if(!eat(p,FE_TOK_DOT)) break;
if(!is_name(p)){error(p,"expected a name after '.' in unit path");return 0;}
}
return fe_arena_strdup(&p->ast->arena,buf,len);
}
FeNode *fe_parse_unit(FeParser *p)
{
FeToken t=p->current; FeNode *root; char *path;
if(!eat(p,FE_TOK_UNIT)){error(p,"source must start with 'unit'");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"unit",4);}
root=toknode(p,FE_N_UNIT,t);
path=unit_path(p);
if(path) root->text=path; else error(p,"expected unit name");
want(p,FE_TOK_SEMI,"expected ';' after unit name");
while(eat(p,FE_TOK_IMPORT)){
FeToken it=p->previous;FeNode *i=toknode(p,FE_N_IMPORT,it);
path=unit_path(p);
if(path) i->text=path; else error(p,"expected import name");
/* `as` renames the binding; without it the binding is the last segment. */
if(eat(p,FE_TOK_AS)) {
if(is_name(p)){i->aux_text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);next(p);}
else error(p,"expected an alias name after 'as'");
}
want(p,FE_TOK_SEMI,"expected ';' after import");
fe_node_add(root,i);
}
while(!is(p,FE_TOK_EOF)){FeNode *d=decl(p);if(d)fe_node_add(root,d);}
return root;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef FE_PARSER_H
#define FE_PARSER_H
#include "ast.h"
typedef struct FeParser {
FeLexer lexer;
FeToken current;
FeToken previous;
FeAst *ast;
FeDiags *diags;
int forbid_struct_literal;
} FeParser;
void fe_parser_init(FeParser *p, FeAst *ast, const char *src, unsigned long length, const char *file, FeDiags *d);
FeNode *fe_parse_unit(FeParser *p);
#endif
+120
View File
@@ -0,0 +1,120 @@
#include "report.h"
#include <string.h>
/* What `--report-unsafe` and `--report-instances` print.
*
* Both answer a question that is easy to ask and easy to let slide: how much of
* the program is outside what the checker can promise, and how much code the
* generic instances are about to become. A number nobody can produce is not a
* budget, so these are here rather than in a comment somewhere. */
typedef struct Counts {
unsigned unsafe_blocks;
unsigned raw_types;
unsigned unchecked_calls;
} Counts;
/* Does this name end in `_unchecked`? Those are the deliberate holes in the
checked surface, and they are worth counting separately from `unsafe`
because they do not need a block around them. */
static int is_unchecked(const char *name)
{
unsigned long n;
unsigned long m = 10UL; /* strlen("_unchecked") */
if (!name) return 0;
n = (unsigned long)strlen(name);
if (n < m) return 0;
return strcmp(name + (n - m), "_unchecked") == 0;
}
static void walk(const FeNode *n, Counts *c)
{
const FeNode *x;
if (!n) return;
if (n->kind == FE_N_UNSAFE) ++c->unsafe_blocks;
if (n->kind == FE_N_TYPE && n->text && strcmp(n->text, "*") == 0)
++c->raw_types;
if (n->kind == FE_N_CALL) {
const char *callee = n->text;
if (!callee && n->a) {
if (n->a->kind == FE_N_IDENT) callee = n->a->text;
else if (n->a->kind == FE_N_MEMBER && n->a->b)
callee = n->a->b->text;
}
if (is_unchecked(callee)) ++c->unchecked_calls;
}
walk(n->a, c);
walk(n->b, c);
walk(n->c, c);
for (x = n->children; x; x = x->next) walk(x, c);
}
/* The standard library is where the unchecked things are supposed to live, so
it is reported but kept out of the total a program is judged on. */
static int is_std(const char *unit)
{
return unit && strncmp(unit, "std.", 4) == 0;
}
void fe_report_unsafe(const FeBuild *build, FILE *out)
{
unsigned u;
Counts total;
Counts outside;
total.unsafe_blocks = 0; total.raw_types = 0; total.unchecked_calls = 0;
outside = total;
fprintf(out, "%-20s %8s %8s %10s\n", "unit", "unsafe", "*T", "unchecked");
for (u = 0; u < build->count; ++u) {
const FeUnit *unit = &build->units[u];
Counts c;
c.unsafe_blocks = 0; c.raw_types = 0; c.unchecked_calls = 0;
walk(unit->ast.root, &c);
if (!c.unsafe_blocks && !c.raw_types && !c.unchecked_calls) continue;
fprintf(out, "%-20s %8u %8u %10u\n", unit->name, c.unsafe_blocks,
c.raw_types, c.unchecked_calls);
total.unsafe_blocks += c.unsafe_blocks;
total.raw_types += c.raw_types;
total.unchecked_calls += c.unchecked_calls;
if (!is_std(unit->name)) {
outside.unsafe_blocks += c.unsafe_blocks;
outside.raw_types += c.raw_types;
outside.unchecked_calls += c.unchecked_calls;
}
}
fprintf(out, "%-20s %8u %8u %10u\n", "total", total.unsafe_blocks,
total.raw_types, total.unchecked_calls);
fprintf(out, "%-20s %8u %8u %10u\n", "outside std", outside.unsafe_blocks,
outside.raw_types, outside.unchecked_calls);
}
void fe_report_instances(const FeCheck *c, FILE *out)
{
unsigned i;
unsigned types = 0;
unsigned methods = 0;
unsigned long bytes = 0;
fprintf(out, "%-52s %6s %8s\n", "instance", "kind", "size");
for (i = 0; i < c->instance_count; ++i) {
const FeInstance *inst = &c->instances[i];
unsigned long size = 0;
if (inst->owner) ++methods;
else {
++types;
/* A struct instance is code only through its methods; what it
costs on its own is the storage one value of it takes. */
{
const FeType *t;
for (t = c->types.types; t; t = t->next)
if (t->name[0] && !strcmp(t->name, inst->key)) {
size = t->size;
break;
}
}
bytes += size;
}
fprintf(out, "%-52s %6s %8lu\n", inst->key,
inst->owner ? "method" : "type", size);
}
fprintf(out, "\n%u instances: %u types (%lu bytes of storage), %u methods\n",
c->instance_count, types, bytes, methods);
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef FE_REPORT_H
#define FE_REPORT_H
#include "check.h"
#include <stdio.h>
/* How much of the build is outside what the checker promises. */
void fe_report_unsafe(const FeBuild *build, FILE *out);
/* What the generic instances came to. */
void fe_report_instances(const FeCheck *c, FILE *out);
#endif
+328
View File
@@ -0,0 +1,328 @@
#include "resolve.h"
#include "parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int segment_ok(const char *s, unsigned long n, const char **why)
{
unsigned long i;
if (!n) { *why = "unit path segment is empty"; return 0; }
if (n > FE_UNIT_SEGMENT_MAX) {
*why = "unit path segment is longer than eight characters";
return 0;
}
if (s[0] < 'a' || s[0] > 'z') {
*why = "unit path segment must start with a lowercase letter";
return 0;
}
for (i = 1; i < n; ++i) {
char c = s[i];
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') continue;
*why = "unit path segment may only contain lowercase letters, digits and '_'";
return 0;
}
return 1;
}
/* Compare a dotted unit path against the source path it was read from.
`game.world.map` matches `.../game/world/map.fe` and nothing else. Only the
trailing segments are compared, since the leading part is the import root. */
static int path_matches(const char *unit, const char *source)
{
unsigned long ulen = strlen(unit), slen = strlen(source);
unsigned long u, s;
if (slen < 3 || strcmp(source + slen - 3, ".fe") != 0) return 0;
slen -= 3;
u = ulen;
s = slen;
while (u > 0) {
char uc, sc;
--u;
if (s == 0) return 0;
--s;
uc = unit[u];
sc = source[s];
if (uc == '.') {
if (sc != '/' && sc != '\\') return 0;
continue;
}
/* Host filesystems may be case-insensitive; the unit name is the
authority and is lowercase by 8.1, so fold the path side down. */
if (sc >= 'A' && sc <= 'Z') sc = (char)(sc - 'A' + 'a');
if (uc != sc) return 0;
}
/* What remains of the source path is the import root, and must end there. */
return s == 0 || source[s - 1] == '/' || source[s - 1] == '\\';
}
static char *read_source(const char *path, unsigned long *size)
{
FILE *f;
long n;
char *p;
f = fopen(path, "rb");
if (!f) return 0;
fseek(f, 0, SEEK_END);
n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return 0; }
p = (char *)malloc((unsigned long)n + 1);
if (!p) { fclose(f); return 0; }
if (fread(p, 1, (unsigned long)n, f) != (unsigned long)n) {
fclose(f); free(p); return 0;
}
p[n] = 0;
fclose(f);
*size = (unsigned long)n;
return p;
}
/* <root>/a/b.fe for the unit a.b */
static void unit_source_path(char *out, unsigned long cap,
const char *root, const char *unit)
{
unsigned long i = 0, j = 0;
while (root[i] && j + 1 < cap) out[j++] = root[i++];
if (j && out[j - 1] != '/' && out[j - 1] != '\\' && j + 1 < cap) out[j++] = '/';
for (i = 0; unit[i] && j + 1 < cap; ++i)
out[j++] = unit[i] == '.' ? '/' : unit[i];
if (j + 3 < cap) { out[j++] = '.'; out[j++] = 'f'; out[j++] = 'e'; }
out[j] = 0;
}
/* Strip the unit's own path from the file it was read from; what is left is
the import root that every other unit is looked up under. */
static void import_root(char *out, unsigned long cap,
const char *source, const char *unit)
{
unsigned long slen = strlen(source);
unsigned long dots = 0, i, cut;
for (i = 0; unit[i]; ++i) if (unit[i] == '.') ++dots;
if (slen >= 3) slen -= 3;
cut = slen;
for (i = 0; i <= dots; ++i) {
while (cut > 0 && source[cut - 1] != '/' && source[cut - 1] != '\\') --cut;
if (i < dots && cut > 0) --cut;
}
if (cut >= cap) cut = cap - 1;
memcpy(out, source, cut);
out[cut] = 0;
if (!cut) { out[0] = '.'; out[1] = 0; }
}
static FeUnit *find_unit(FeBuild *b, const char *name)
{
unsigned i;
for (i = 0; i < b->count; ++i)
if (strcmp(b->units[i].name, name) == 0) return &b->units[i];
return 0;
}
const char *fe_import_binding(const FeNode *import)
{
const char *dot;
if (!import) return 0;
if (import->aux_text) return import->aux_text;
dot = import->text ? strrchr(import->text, '.') : 0;
return dot ? dot + 1 : import->text;
}
int fe_resolve_unit_identity(FeAst *ast, FeDiags *diags, const char *source_path)
{
FeNode *root = ast ? ast->root : 0;
const char *name, *why;
const char *seg;
unsigned long i, len;
int ok = 1;
if (!root || root->kind != FE_N_UNIT || !root->text) return 0;
name = root->text;
len = strlen(name);
seg = name;
for (i = 0; i <= len; ++i) {
if (i != len && name[i] != '.') continue;
if (!segment_ok(seg, (unsigned long)(name + i - seg), &why)) {
fe_diag_error(diags, root->loc, why);
ok = 0;
}
seg = name + i + 1;
}
if (ok && source_path && !path_matches(name, source_path)) {
fe_diag_errorf(diags, root->loc,
"unit %s must be declared in a source file matching its path",
name);
ok = 0;
}
return ok;
}
/* Depth-first load. `stack` is the chain of units currently being loaded, so
meeting one again is a cycle rather than a repeat visit. */
static int load_unit(FeBuild *b, const char *name, FeLoc from, int have_from,
const char **stack, unsigned depth)
{
FeUnit *unit;
FeNode *n;
FeParser p;
unsigned long size;
unsigned i;
int ok = 1;
for (i = 0; i < depth; ++i) {
if (strcmp(stack[i], name) == 0) {
fe_diag_errorf(b->diags, from, "import of %s forms a cycle", name);
return 0;
}
}
if (find_unit(b, name)) return 1;
if (b->count >= FE_BUILD_UNIT_MAX) {
fe_diag_error(b->diags, from, "too many units in one build");
return 0;
}
if (strlen(name) >= FE_UNIT_PATH_MAX) {
fe_diag_errorf(b->diags, from, "unit path is too long: %s", name);
return 0;
}
unit = &b->units[b->count];
memset(unit, 0, sizeof *unit);
strcpy(unit->name, name);
/* `std` is reserved (SPEC 10) and lives with the compiler, not with the
program, so it is looked up under its own root. */
unit_source_path(unit->path, sizeof unit->path,
(name[0]=='s' && name[1]=='t' && name[2]=='d' &&
(name[3]=='.' || name[3]==0) && b->std_root[0])
? b->std_root : b->root,
name);
unit->source = read_source(unit->path, &size);
if (!unit->source) {
if (have_from)
fe_diag_errorf(b->diags, from, "import %s has no source file", name);
else
fe_diag_errorf(b->diags, from, "cannot open %s", unit->path);
return 0;
}
b->count++;
unit->size = size;
fe_ast_init(&unit->ast);
/* Diagnostics from here on belong to this file. */
fe_diags_source(b->diags, unit->source, size);
fe_parser_init(&p, &unit->ast, unit->source, size, unit->path, b->diags);
unit->ast.root = fe_parse_unit(&p);
unit->loaded = 1;
if (!fe_resolve_unit_identity(&unit->ast, b->diags, unit->path)) ok = 0;
stack[depth] = unit->name;
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) {
if (n->kind != FE_N_IMPORT || !n->text) continue;
/* The import statement is where the reader has to make a change, so
the diagnostic points there rather than at the unit it names. */
if (!load_unit(b, n->text, n->loc, 1, stack, depth + 1)) ok = 0;
fe_diags_source(b->diags, unit->source, unit->size);
}
stack[depth] = 0;
return ok;
}
/* A binding names one unit inside one importer; two imports cannot claim it. */
static int check_bindings(FeBuild *b, FeUnit *unit)
{
FeNode *n, *m;
int ok = 1;
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) {
const char *a;
if (n->kind != FE_N_IMPORT) continue;
a = fe_import_binding(n);
if (!a) continue;
for (m = unit->ast.root->children; m != n; m = m->next) {
const char *other;
if (m->kind != FE_N_IMPORT) continue;
other = fe_import_binding(m);
if (other && strcmp(a, other) == 0) {
fe_diag_errorf(b->diags, n->loc,
"import binding %s is already taken; use an alias", a);
ok = 0;
}
}
}
return ok;
}
int fe_build_load(FeBuild *build, const char *entry, FeDiags *diags,
const char *std_root)
{
const char *stack[FE_BUILD_UNIT_MAX];
FeAst probe;
FeParser p;
char *source;
char name[FE_UNIT_PATH_MAX];
FeLoc loc;
unsigned long size;
unsigned i;
int ok;
memset(build, 0, sizeof *build);
build->diags = diags;
if (std_root) {
unsigned long k = 0;
while (std_root[k] && k + 1 < sizeof build->std_root) {
build->std_root[k] = std_root[k];
++k;
}
build->std_root[k] = 0;
}
/* The entry file fixes the import root, so it has to be parsed far enough
to know its own name before anything else can be found. */
source = read_source(entry, &size);
if (!source) {
FeLoc none;
none.file = entry; none.line = 0; none.col = 0;
fe_diag_errorf(diags, none, "cannot open %s", entry);
return 0;
}
fe_ast_init(&probe);
fe_parser_init(&p, &probe, source, size, entry, diags);
probe.root = fe_parse_unit(&p);
if (!probe.root || !probe.root->text || diags->errors) {
fe_ast_destroy(&probe);
free(source);
return 0;
}
import_root(build->root, sizeof build->root, entry, probe.root->text);
strncpy(name, probe.root->text, sizeof name - 1);
name[sizeof name - 1] = 0;
loc = probe.root->loc;
fe_ast_destroy(&probe);
free(source);
ok = load_unit(build, name, loc, 0, stack, 0);
for (i = 0; i < build->count; ++i)
if (!check_bindings(build, &build->units[i])) ok = 0;
return ok && diags->errors == 0;
}
void fe_build_destroy(FeBuild *build)
{
unsigned i;
for (i = 0; i < build->count; ++i) {
if (build->units[i].loaded) fe_ast_destroy(&build->units[i].ast);
free(build->units[i].source);
}
build->count = 0;
}
FeUnit *fe_build_binding(FeBuild *build, FeUnit *unit, const char *binding)
{
FeNode *n;
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) {
const char *bound;
if (n->kind != FE_N_IMPORT || !n->text) continue;
bound = fe_import_binding(n);
if (bound && strcmp(bound, binding) == 0) return find_unit(build, n->text);
}
return 0;
}
+63
View File
@@ -0,0 +1,63 @@
#ifndef FE_RESOLVE_H
#define FE_RESOLVE_H
#include "ast.h"
#include "diag.h"
/* Unit-level resolution: identity, import bindings, and the unit graph.
This runs between parsing and semantic checking. It answers questions that
need more than one file -- what a unit is called, what it imports, and
whether those imports exist and terminate -- so that check.c can keep
looking at one function at a time. */
/* SPEC 8.1: each segment is ASCII lowercase, starts with a letter, continues
with letters, digits or '_', and is at most eight characters. The limit is
what makes a unit path map to a FAT/DOS 8.3 source path unambiguously. */
#define FE_UNIT_SEGMENT_MAX 8
#define FE_UNIT_PATH_MAX 128
#define FE_BUILD_UNIT_MAX 256
typedef struct FeUnit {
char name[FE_UNIT_PATH_MAX]; /* canonical dotted path */
char path[260]; /* source file it was read from */
FeAst ast;
char *source; /* owned; freed with the build */
unsigned long size;
int loaded;
int checked;
} FeUnit;
typedef struct FeBuild {
FeUnit units[FE_BUILD_UNIT_MAX];
unsigned count;
char root[260]; /* import root: where unit paths start */
/* Where `std.*` is looked for. The standard library is not under the
program's root -- it ships with the compiler. */
char std_root[260];
FeDiags *diags;
} FeBuild;
/* Validate the `unit` declaration against SPEC 8.1, and against the file it was
read from: the path must match the dotted name, so `game.world.map` has to
come from `game/world/map.fe`. `source_path` may be null to skip that half.
Returns non-zero when the unit is well formed. */
int fe_resolve_unit_identity(FeAst *ast, FeDiags *diags, const char *source_path);
/* Load `entry` and everything it imports, transitively.
The import root is derived from the entry file: a unit named `a.b` read from
`<root>/a/b.fe` fixes `<root>`, so a sibling `import c.d;` is looked for at
`<root>/c/d.fe`. Reports missing imports, import cycles, and binding
conflicts. Returns non-zero when the whole graph loaded cleanly. */
int fe_build_load(FeBuild *build, const char *entry, FeDiags *diags,
const char *std_root);
void fe_build_destroy(FeBuild *build);
/* The unit a binding refers to inside `unit`, or null.
The binding is the last segment of the import path unless `as` renamed it. */
FeUnit *fe_build_binding(FeBuild *build, FeUnit *unit, const char *binding);
/* The local name an import introduces: its alias, or the last path segment. */
const char *fe_import_binding(const FeNode *import);
#endif
+782
View File
@@ -0,0 +1,782 @@
#include "types.h"
#include "m7.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static FeType *new_type(FeTypeCtx *ctx, const char *name, FeTypeKind kind)
{
FeType *t;
unsigned i;
t = (FeType *)fe_arena_alloc(ctx->arena, sizeof(FeType));
if (!t) return 0;
for (i = 0; i + 1U < sizeof(t->name) && name && name[i]; ++i)
t->name[i] = name[i];
t->name[i] = '\0';
t->kind = kind;
t->unit = 0;
t->cname = 0;
t->maker = 0;
t->none_cname = 0;
t->unwrap_cname = 0;
t->indexer = 0;
t->slicer = 0;
t->full_slicer = 0;
t->tail_slicer = 0;
t->drop_cname = 0;
t->alloc_cname = 0;
t->replace_cname = 0;
t->bits = 0;
t->is_unsigned = 0;
t->packed = 0;
t->is_error = 0;
t->has_drop = 0;
t->length = 0;
t->size = 0;
t->align = 1;
t->elem = 0;
t->error_value = 0;
t->ref_mut = 0;
t->fields = 0;
t->field_count = 0;
t->variants = 0;
t->variant_count = 0;
t->serial = ctx->generated_serial++;
t->decl_node = 0;
t->bind_count = 0;
t->next = ctx->types;
t->emit_state = 0;
t->cycle_state = 0;
t->layout_state = 0;
t->building = 0;
ctx->types = t;
return t;
}
void fe_types_init(FeTypeCtx *ctx, FeArena *arena, unsigned pointer_bits)
{
ctx->arena = arena;
ctx->types = 0;
ctx->pointer_bits = pointer_bits;
ctx->unit_name = "unit";
ctx->generated_serial = 0;
ctx->param_count = 0;
ctx->instantiate = 0;
ctx->instantiate_owner = 0;
ctx->enter_decl = 0;
ctx->leave_decl = 0;
}
/* Does this type answer to `name` for someone checking `unit`? A type with no
unit is shared by everyone; one with a unit answers only inside it. */
static int type_visible_as(const FeType *t, const char *unit, const char *name)
{
if (strcmp(t->name, name) != 0) return 0;
if (!t->unit) return 1;
return unit && strcmp(t->unit, unit) == 0;
}
FeType *fe_type_intern_unit(FeTypeCtx *ctx, const char *unit, const char *name)
{
FeType *t;
if (!name) name = "<unknown>";
if (!unit) return fe_type_intern(ctx, name);
for (t = ctx->types; t; t = t->next)
if (t->unit && strcmp(t->name, name) == 0 &&
strcmp(t->unit, unit) == 0) return t;
t = new_type(ctx, name, FE_TYPE_UNKNOWN);
if (t) t->unit = unit;
return t;
}
FeType *fe_type_intern(FeTypeCtx *ctx, const char *name)
{
FeType *t;
unsigned bits = 0;
int uns = 0;
FeTypeKind kind = FE_TYPE_UNKNOWN;
unsigned i;
if (!name) name = "<unknown>";
/* A bound type parameter is its argument, and shadows everything. */
for (i = 0; i < ctx->param_count; ++i)
if (strcmp(ctx->params[i].name, name) == 0) return ctx->params[i].type;
for (t = ctx->types; t; t = t->next)
if (type_visible_as(t, ctx->unit_name, name)) return t;
if (strcmp(name, "void") == 0) kind = FE_TYPE_VOID;
else if (strcmp(name, "bool") == 0) kind = FE_TYPE_BOOL;
else if (strcmp(name, "char") == 0) kind = FE_TYPE_CHAR;
else if (strcmp(name, "str") == 0)
return fe_type_slice(ctx, fe_type_intern(ctx, "u8"));
else if (strcmp(name, "io.Writer") == 0) {
kind = FE_TYPE_STRUCT;
}
/* The default error set. Its members are collected across the build rather
than declared, so it carries an identity but no variant list. */
else if (strcmp(name, "core.Error") == 0) {
kind = FE_TYPE_ENUM; bits = 16; uns = 1;
}
else if (strcmp(name, "i8") == 0 || strcmp(name, "u8") == 0) {
kind = FE_TYPE_INT; bits = 8; uns = name[0] == 'u';
} else if (strcmp(name, "i16") == 0 || strcmp(name, "u16") == 0) {
kind = FE_TYPE_INT; bits = 16; uns = name[0] == 'u';
} else if (strcmp(name, "i32") == 0 || strcmp(name, "u32") == 0) {
kind = FE_TYPE_INT; bits = 32; uns = name[0] == 'u';
} else if (strcmp(name, "usize") == 0 || strcmp(name, "isize") == 0) {
kind = FE_TYPE_INT; bits = ctx->pointer_bits; uns = name[0] == 'u';
}
t = new_type(ctx, name, kind);
if (!t) return 0;
t->bits = bits;
t->is_unsigned = uns;
if (strcmp(name,"core.Error")==0) { t->is_error = 1; t->size = 2; t->align = 2; }
if (strcmp(name,"io.Writer")==0) {
t->cname=fe_arena_strdup(ctx->arena,"fe_writer",10);
t->size=4;
t->align=1;
return t;
}
if (kind == FE_TYPE_STR) {
t->cname = fe_arena_strdup(ctx->arena, "fe_str", 6);
t->elem = fe_type_intern(ctx, "u8");
t->indexer = "fe_idx_str";
t->slicer = "fe_slice_str";
t->full_slicer = "fe_full_slice_str";
t->tail_slicer = "fe_tail_slice_str";
}
return t;
}
static char *generated_name(FeTypeCtx *ctx, const char *prefix,
const char *name)
{
char number[24];
unsigned long n;
char *p;
sprintf(number, "%u", ctx->generated_serial++);
n = (unsigned long)strlen(prefix) + (unsigned long)strlen(name) +
(unsigned long)strlen(number) + 2UL;
p = (char *)fe_arena_alloc(ctx->arena, n);
if (!p) return 0;
strcpy(p, prefix);
strcat(p, name);
strcat(p, "_");
strcat(p, number);
return p;
}
FeType *fe_type_array(FeTypeCtx *ctx, unsigned long length, FeType *elem)
{
char key[320];
FeType *t;
sprintf(key, "[%lu]%s", length, elem ? elem->name : "?");
t = fe_type_intern(ctx, key);
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_ARRAY;
t->length = length;
t->elem = elem;
t->cname = generated_name(ctx, "struct fe_arr_", "type");
t->maker = generated_name(ctx, "fe_make_arr_", "type");
t->drop_cname = generated_name(ctx, "fe_drop_arr_", "type");
t->indexer = generated_name(ctx, "fe_idx_arr_", "type");
t->slicer = generated_name(ctx, "fe_slice_arr_", "type");
t->full_slicer = generated_name(ctx, "fe_full_arr_", "type");
t->tail_slicer = generated_name(ctx, "fe_tail_arr_", "type");
}
return t;
}
FeType *fe_type_slice(FeTypeCtx *ctx, FeType *elem)
{
char key[320];
FeType *t;
sprintf(key, "[]%s", elem ? elem->name : "?");
t = fe_type_intern(ctx, key);
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_SLICE;
t->elem = elem;
t->cname = generated_name(ctx, "fe_slice_", "type");
t->maker = generated_name(ctx, "fe_make_slice_", "type");
t->indexer = generated_name(ctx, "fe_idx_slice_", "type");
t->slicer = generated_name(ctx, "fe_slice_slice_", "type");
t->full_slicer = generated_name(ctx, "fe_full_slice_", "type");
t->tail_slicer = generated_name(ctx, "fe_tail_slice_", "type");
}
return t;
}
FeType *fe_type_mut_slice(FeTypeCtx *ctx, FeType *elem)
{
char key[320];
FeType *t;
sprintf(key, "[]mut %s", elem ? elem->name : "?");
t = fe_type_intern(ctx, key);
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_SLICE;
t->elem = elem;
t->ref_mut = 1;
t->cname = generated_name(ctx, "fe_mut_slice_", "type");
t->maker = generated_name(ctx, "fe_make_mut_slice_", "type");
t->indexer = generated_name(ctx, "fe_idx_mut_slice_", "type");
t->slicer = generated_name(ctx, "fe_slice_mut_slice_", "type");
t->full_slicer = generated_name(ctx, "fe_full_mut_slice_", "type");
t->tail_slicer = generated_name(ctx, "fe_tail_mut_slice_", "type");
}
return t;
}
FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable)
{
char key[320];
FeType *t;
sprintf(key,"%s%s",mutable ? "&mut " : "&",elem ? elem->name : "?");
t=fe_type_intern(ctx,key);
if(t->kind==FE_TYPE_UNKNOWN) {
t->kind=FE_TYPE_REF;
t->elem=elem;
t->ref_mut=mutable;
}
return t;
}
FeType *fe_type_raw(FeTypeCtx *ctx, FeType *elem)
{
char key[320];
FeType *t;
sprintf(key, "*%s", elem ? elem->name : "?");
t = fe_type_intern(ctx, key);
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_RAW;
t->elem = elem;
t->size = FE_PTR_SIZE;
t->align = FE_PTR_ALIGN;
}
return t;
}
FeType *fe_type_owned(FeTypeCtx *ctx, FeType *elem)
{
char key[320];
FeType *t;
sprintf(key,"^%s",elem ? elem->name : "?");
t=fe_type_intern(ctx,key);
if(t->kind==FE_TYPE_UNKNOWN) {
t->kind=FE_TYPE_OWNED;
t->elem=elem;
if(elem && elem->kind==FE_TYPE_SLICE) {
t->cname=generated_name(ctx,"fe_owned_slice_","type");
t->maker=generated_name(ctx,"fe_make_owned_slice_","type");
}
}
return t;
}
FeType *fe_type_error_union(FeTypeCtx *ctx, FeType *value)
{
char key[320];
FeType *t;
sprintf(key,"!%s",value ? value->name : "?");
t=fe_type_intern(ctx,key);
if(t->kind==FE_TYPE_UNKNOWN) {
t->kind=FE_TYPE_ERROR_UNION;
t->error_value=value;
t->drop_cname=generated_name(ctx,"fe_drop_result_","value");
if (value && value->kind != FE_TYPE_VOID) {
t->cname=generated_name(ctx,"struct fe_result_","value");
t->maker=generated_name(ctx,"fe_make_result_","value");
t->none_cname=generated_name(ctx,"fe_fail_result_","value");
t->alloc_cname=generated_name(ctx,"fe_alloc_result_","value");
}
}
return t;
}
void fe_type_require_replace(FeTypeCtx *ctx, FeType *type)
{
if(type && !type->replace_cname)
type->replace_cname=generated_name(ctx,"fe_replace_","type");
}
FeType *fe_type_declare_struct(FeTypeCtx *ctx, const FeNode *node, int packed)
{
FeType *t;
FeNode *f;
unsigned count = 0;
unsigned i = 0;
char *cname;
if (!node || !node->text) return 0;
t = fe_type_intern_unit(ctx, ctx->unit_name, node->text);
if (t->kind != FE_TYPE_UNKNOWN && t->kind != FE_TYPE_STRUCT) return t;
if (t->kind == FE_TYPE_STRUCT) return t;
t->kind = FE_TYPE_STRUCT;
t->packed = packed;
t->decl_node = node;
for (f = node->children; f; f = f->next)
if (f->kind==FE_N_FN && f->text && strcmp(f->text,"drop")==0)
t->has_drop=1;
cname = (char *)fe_arena_alloc(ctx->arena,
(unsigned long)strlen("struct fe_") + strlen(ctx->unit_name) +
strlen(node->text) + 2UL);
if (!cname) return t;
strcpy(cname, "struct fe_");
strcat(cname, ctx->unit_name);
strcat(cname, "_");
strcat(cname, node->text);
t->cname = cname;
t->maker = generated_name(ctx, "fe_make_", node->text);
t->drop_cname = generated_name(ctx, "fe_drop_", node->text);
for (f = node->children; f; f = f->next)
if (f->kind == FE_N_FIELD) ++count;
t->field_count = count;
if (count) {
t->fields = (FeFieldType *)fe_arena_alloc(ctx->arena,
count * sizeof(FeFieldType));
if (!t->fields) return t;
for (f = node->children; f; f = f->next) if (f->kind == FE_N_FIELD) {
t->fields[i].name = f->text;
t->fields[i].type = 0;
t->fields[i].offset = 0;
t->fields[i].ast_node = f;
++i;
}
}
return t;
}
FeType *fe_type_declare_enum(FeTypeCtx *ctx, const FeNode *node)
{
FeType *t;
FeNode *v;
unsigned count = 0;
unsigned i = 0;
char *cname;
if (!node || !node->text) return 0;
t = fe_type_intern_unit(ctx, ctx->unit_name, node->text);
if (t->kind != FE_TYPE_UNKNOWN && t->kind != FE_TYPE_ENUM) return t;
if (t->kind == FE_TYPE_ENUM) return t;
t->kind = FE_TYPE_ENUM;
t->decl_node = node;
cname = (char *)fe_arena_alloc(ctx->arena,
(unsigned long)strlen("struct fe_") + strlen(ctx->unit_name) +
strlen(node->text) + 2UL);
if (!cname) return t;
strcpy(cname, "struct fe_");
strcat(cname, ctx->unit_name);
strcat(cname, "_");
strcat(cname, node->text);
t->cname = cname;
for (v = node->children; v; v = v->next) ++count;
t->variant_count = count;
if (count) {
t->variants = (FeVariantType *)fe_arena_alloc(ctx->arena,
count * sizeof(FeVariantType));
if (!t->variants) return t;
for (v = node->children; v; v = v->next) {
t->variants[i].name = v->text;
t->variants[i].fields = 0;
t->variants[i].field_count = 0;
if (node->kind==FE_N_ERROR_DECL && v->a &&
v->a->kind==FE_N_LITERAL && v->a->text)
t->variants[i].tag=(unsigned)strtoul(v->a->text,0,0);
else t->variants[i].tag = i;
t->variants[i].ast_node = v;
t->variants[i].maker = generated_name(ctx, "fe_make_variant_", v->text ? v->text : "variant");
if (v->a && v->a->kind == FE_N_TYPE) {
t->variants[i].field_count = 1;
t->variants[i].fields = (FeFieldType *)fe_arena_alloc(ctx->arena, sizeof(FeFieldType));
if (t->variants[i].fields) {
t->variants[i].fields[0].name = "value";
t->variants[i].fields[0].type = fe_type_from_ast(ctx, v->a);
t->variants[i].fields[0].offset = 0;
t->variants[i].fields[0].ast_node = v;
}
}
if (!v->a) {
FeNode *f;
unsigned fc = 0;
unsigned j = 0;
for (f = v->children; f; f = f->next)
if (f->kind == FE_N_FIELD) ++fc;
t->variants[i].field_count = fc;
if (fc) {
t->variants[i].fields = (FeFieldType *)fe_arena_alloc(
ctx->arena, fc * sizeof(FeFieldType));
if (t->variants[i].fields) for (f = v->children; f; f=f->next)
if (f->kind == FE_N_FIELD) {
t->variants[i].fields[j].name = f->text;
t->variants[i].fields[j].type = 0;
t->variants[i].fields[j].offset = 0;
t->variants[i].fields[j].ast_node = f;
++j;
}
}
}
++i;
}
}
return t;
}
FeType *fe_type_declare_error(FeTypeCtx *ctx, const FeNode *node)
{
FeType *t=fe_type_declare_enum(ctx,node);
if (t) t->is_error=1;
return t;
}
static unsigned long round_up(unsigned long x, unsigned a)
{
unsigned long rem;
if (a <= 1U) return x;
rem = x % (unsigned long)a;
return rem ? x + (unsigned long)a - rem : x;
}
unsigned long fe_type_size(const FeType *t)
{
return t ? t->size : 0;
}
unsigned long fe_type_payload_offset(const FeType *t)
{
if (!t) return 0;
if (t->kind == FE_TYPE_ERROR_UNION) {
if (!t->error_value || t->error_value->kind == FE_TYPE_VOID) return 2;
return round_up(2UL, fe_type_align(t->error_value));
}
if (t->kind == FE_TYPE_OPTIONAL) {
if (fe_m7_optional_uses_niche(t->elem)) return 0;
return round_up(1UL, fe_type_align(t->elem));
}
if (t->kind == FE_TYPE_ENUM) return round_up(t->bits / 8U, t->align);
return 0;
}
unsigned fe_type_align(const FeType *t)
{
return t && t->align ? t->align : 1U;
}
/* Resolve this type's fields where they were written. Without the callback
installed -- or for a type nobody declared -- everything stays where it is,
which is what the non-checking users of this layer want. */
static int enter_decl_unit(FeTypeCtx *ctx, const char *unit, const char **was)
{
*was = ctx->unit_name;
if (!ctx->enter_decl || !unit) return -1;
return ctx->enter_decl(ctx->instantiate_owner, unit);
}
/* Put back both halves: the unit the checker was in, and the name this layer
was interning under -- an instantiation moves the second without the
first, so restoring one is not restoring the other. */
static void leave_decl_unit(FeTypeCtx *ctx, int back, const char *was)
{
if (back >= 0 && ctx->leave_decl)
ctx->leave_decl(ctx->instantiate_owner, back);
ctx->unit_name = was;
}
/* Did every field end up with a size? A struct whose members are not settled
cannot be settled either -- and freezing it here is worse than leaving it,
because nothing recomputes a type that already has a size. */
static int members_ready(const FeType *t)
{
unsigned i;
unsigned j;
if (t->kind == FE_TYPE_STRUCT) {
for (i = 0; i < t->field_count; ++i) {
if (!t->fields[i].type) return 0;
if (t->fields[i].type->layout_state != 2) return 0;
}
return 1;
}
for (i = 0; i < t->variant_count; ++i)
for (j = 0; j < t->variants[i].field_count; ++j) {
if (!t->variants[i].fields[j].type) return 0;
if (t->variants[i].fields[j].type->layout_state != 2) return 0;
}
return 1;
}
static void layout_type(FeTypeCtx *ctx, FeType *t)
{
unsigned i;
unsigned align;
unsigned long off;
unsigned long max_size;
unsigned max_align;
if (!t || t->size) return;
if (t->building) return;
if (t->layout_state == 1) {
t->size = 1;
t->align = 1;
return;
}
t->layout_state = 1;
if (t->kind == FE_TYPE_VOID || t->kind == FE_TYPE_UNKNOWN ||
t->kind == FE_TYPE_ERROR) { t->size = 0; t->align = 1; t->layout_state = 2; return; }
if (t->kind == FE_TYPE_ERROR_UNION) {
if (t->error_value && t->error_value->kind != FE_TYPE_VOID) {
layout_type(ctx,t->error_value);
t->align=fe_type_align(t->error_value);
t->size=round_up(2UL,t->align)+fe_type_size(t->error_value);
t->size=round_up(t->size,t->align);
} else {
t->size=2;
t->align=2U;
}
t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_OPTIONAL) {
layout_type(ctx,t->elem);
if (fe_m7_optional_uses_niche(t->elem)) {
t->size=fe_type_size(t->elem);
t->align=fe_type_align(t->elem);
} else {
t->align=fe_type_align(t->elem);
t->size=round_up(1UL,t->align)+fe_type_size(t->elem);
t->size=round_up(t->size,t->align);
}
t->layout_state=2; return;
}
if (t->kind == FE_TYPE_BOOL || t->kind == FE_TYPE_CHAR) {
t->size = 1; t->align = 1; t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_INT) {
t->size = (t->bits + 7U) / 8U;
t->align = t->size;
if (t->size > 4UL) t->size = 4UL;
t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_REF || t->kind == FE_TYPE_RAW) {
t->size = FE_PTR_SIZE;
t->align = FE_PTR_ALIGN;
t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_OWNED) {
t->size = t->elem && t->elem->kind==FE_TYPE_SLICE ?
2UL * FE_PTR_SIZE : FE_PTR_SIZE;
t->align = FE_PTR_ALIGN;
t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_SLICE || t->kind == FE_TYPE_STR) {
t->size = 2UL * FE_PTR_SIZE;
t->align = FE_PTR_ALIGN;
t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_ARRAY) {
layout_type(ctx, t->elem);
t->align = t->packed ? 1U : fe_type_align(t->elem);
t->size = t->length * fe_type_size(t->elem);
t->layout_state = 2; return;
}
if (t->kind == FE_TYPE_STRUCT) {
const char *was;
int back = enter_decl_unit(ctx, t->unit, &was);
for (i = 0; i < t->field_count; ++i)
if (!t->fields[i].type && t->fields[i].ast_node)
t->fields[i].type = fe_type_from_ast(ctx, t->fields[i].ast_node->a);
leave_decl_unit(ctx, back, was);
off = 0; max_align = 1;
for (i = 0; i < t->field_count; ++i) {
layout_type(ctx, t->fields[i].type);
align = t->packed ? 1U : fe_type_align(t->fields[i].type);
if (align > max_align) max_align = align;
off = round_up(off, align);
t->fields[i].offset = off;
off += fe_type_size(t->fields[i].type);
}
if (!members_ready(t)) { t->layout_state = 0; return; }
t->align = max_align;
t->size = round_up(off, max_align);
t->layout_state = 2;
return;
}
if (t->kind == FE_TYPE_ENUM) {
const char *was;
int back = enter_decl_unit(ctx, t->unit, &was);
for (i = 0; i < t->variant_count; ++i) {
unsigned j;
for (j = 0; j < t->variants[i].field_count; ++j)
if (!t->variants[i].fields[j].type && t->variants[i].fields[j].ast_node)
t->variants[i].fields[j].type = fe_type_from_ast(
ctx, t->variants[i].fields[j].ast_node->a);
}
leave_decl_unit(ctx, back, was);
max_size = 0; max_align = 1;
for (i = 0; i < t->variant_count; ++i) {
unsigned j;
off = 0;
for (j = 0; j < t->variants[i].field_count; ++j) {
layout_type(ctx, t->variants[i].fields[j].type);
if (fe_type_align(t->variants[i].fields[j].type) > max_align)
max_align = fe_type_align(t->variants[i].fields[j].type);
/* Where this field sits inside the payload area, which the
code generator needs and nobody was recording. */
off = round_up(off, fe_type_align(t->variants[i].fields[j].type));
t->variants[i].fields[j].offset = off;
off += fe_type_size(t->variants[i].fields[j].type);
}
if (off > max_size) max_size = off;
}
if (!members_ready(t)) { t->layout_state = 0; return; }
t->bits = t->variant_count > 256U ? 16U : 8U;
off = round_up(t->bits / 8U, max_align);
t->size = round_up(off + max_size, max_align);
t->align = max_align;
t->layout_state = 2;
}
}
void fe_type_layout_all(FeTypeCtx *ctx)
{
FeType *t;
int again = 1;
unsigned rounds = 0;
/* One pass settles a type only if everything under it is already settled,
so a type that had to wait is picked up by the next round. Sixteen is
far past any real nesting; it is here so a cycle cannot spin. */
while (again && rounds < 16U) {
again = 0;
for (t = ctx->types; t; t = t->next) {
if (t->size || t->layout_state == 2) continue;
layout_type(ctx, t);
if (t->layout_state == 2) again = 1;
}
++rounds;
}
}
FeFieldType *fe_type_field(FeType *t, const char *name)
{
unsigned i;
if (!t || t->kind != FE_TYPE_STRUCT || !name) return 0;
for (i = 0; i < t->field_count; ++i)
if (strcmp(t->fields[i].name, name) == 0) return &t->fields[i];
return 0;
}
FeVariantType *fe_type_variant(FeType *t, const char *name)
{
unsigned i;
if (!t || t->kind != FE_TYPE_ENUM || !name) return 0;
for (i = 0; i < t->variant_count; ++i)
if (strcmp(t->variants[i].name, name) == 0) return &t->variants[i];
return 0;
}
FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node)
{
unsigned long length = 0;
char qualified[128];
if (!node) return fe_type_intern(ctx, "<unknown>");
if (node->kind != FE_N_TYPE) return fe_type_intern(ctx, "<unknown>");
if (node->text && strcmp(node->text, "as") == 0)
return fe_type_from_ast(ctx, node->b);
if (node->text && strcmp(node->text, "str") == 0)
return fe_type_slice(ctx, fe_type_intern(ctx, "u8"));
if (node->a && node->a->kind==FE_N_IDENT && node->text &&
strcmp(node->text,"io")==0 && node->a->text) {
sprintf(qualified,"%s.%s",node->text,node->a->text);
return fe_type_intern(ctx,qualified);
}
if (node->text && (strcmp(node->text, "&") == 0 ||
strcmp(node->text, "&mut") == 0))
return fe_type_ref(ctx, fe_type_from_ast(ctx,node->a),
strcmp(node->text,"&mut") == 0);
if (node->text && strcmp(node->text,"^")==0)
return fe_type_owned(ctx,fe_type_from_ast(ctx,node->a));
if (node->text && strcmp(node->text,"?")==0)
return fe_m7_optional_type(ctx,fe_type_from_ast(ctx,node->a));
if (node->text && (strcmp(node->text, "[") == 0 ||
strcmp(node->text, "[]mut") == 0)) {
if (node->a) {
if (node->a->kind == FE_N_LITERAL && node->a->text)
length = strtoul(node->a->text, 0, 0);
return fe_type_array(ctx, length, fe_type_from_ast(ctx, node->b));
}
return strcmp(node->text,"[]mut")==0 ?
fe_type_mut_slice(ctx, fe_type_from_ast(ctx,node->b)) :
fe_type_slice(ctx, fe_type_from_ast(ctx, node->b));
}
if (node->text && strcmp(node->text, "!") == 0) {
if (node->b)
return fe_m7_error_union_type(ctx,fe_type_from_ast(ctx,node->a),
fe_type_from_ast(ctx,node->b));
return fe_type_error_union(ctx,fe_type_from_ast(ctx,node->a));
}
if (node->text && strcmp(node->text, "*") == 0)
return fe_type_raw(ctx, fe_type_from_ast(ctx, node->a));
if (node->text && strcmp(node->text, "fn") == 0)
return fe_type_intern(ctx, "<unknown>");
/* A plain named type may be a generic declaration -- with arguments it is
an instance, without them it is a mistake -- and only the checker knows
the declarations, so it decides. */
if (ctx->instantiate)
return ctx->instantiate(ctx->instantiate_owner, node);
return fe_type_intern(ctx, node->text);
}
int fe_type_equal(const FeType *a, const FeType *b)
{
if (a == b) return 1;
if (!a || !b) return 0;
if (strcmp(a->name, b->name) != 0) return 0;
/* The same spelling is not the same type across a unit boundary. */
if (!a->unit || !b->unit) return a->unit == b->unit;
return strcmp(a->unit, b->unit) == 0;
}
int fe_type_is_integer(const FeType *t)
{
return t && t->kind == FE_TYPE_INT;
}
int fe_type_is_indexable(const FeType *t)
{
return t && (t->kind == FE_TYPE_ARRAY || t->kind == FE_TYPE_SLICE ||
t->kind == FE_TYPE_STR);
}
const char *fe_type_c_name(const FeType *t, unsigned pointer_bits)
{
if (!t) return "long";
if (t->kind == FE_TYPE_OPTIONAL && fe_m7_optional_uses_niche(t->elem))
return fe_type_c_name(t->elem,pointer_bits);
if (t->cname) return t->cname;
if (t->kind == FE_TYPE_VOID) return "void";
if (t->kind == FE_TYPE_ERROR_UNION) {
if (t->error_value && t->error_value->kind != FE_TYPE_VOID && t->cname)
return t->cname;
return "unsigned short";
}
if (t->kind == FE_TYPE_BOOL || t->kind == FE_TYPE_CHAR) return "unsigned char";
if (t->kind == FE_TYPE_REF) {
static char ref_name[128];
if (t->ref_mut) {
strcpy(ref_name,fe_type_c_name(t->elem,pointer_bits));
strcat(ref_name," *");
} else {
strcpy(ref_name,"const ");
strcat(ref_name,fe_type_c_name(t->elem,pointer_bits));
strcat(ref_name," *");
}
return ref_name;
}
if (t->kind == FE_TYPE_OWNED) {
static char owned_name[128];
strcpy(owned_name,fe_type_c_name(t->elem,pointer_bits));
strcat(owned_name," *");
return owned_name;
}
if (t->kind != FE_TYPE_INT) return "long";
if (strcmp(t->name, "usize") == 0) return "unsigned long";
if (strcmp(t->name, "isize") == 0) return "long";
if (strcmp(t->name, "i8") == 0) return "signed char";
if (strcmp(t->name, "u8") == 0) return "unsigned char";
if (strcmp(t->name, "i16") == 0) return "short";
if (strcmp(t->name, "u16") == 0) return "unsigned short";
if (strcmp(t->name, "i32") == 0) return "long";
if (strcmp(t->name, "u32") == 0) return "unsigned long";
return "long";
}
+166
View File
@@ -0,0 +1,166 @@
#ifndef FE_TYPES_H
#define FE_TYPES_H
#include "ast.h"
typedef enum FeTypeKind {
FE_TYPE_ERROR, FE_TYPE_ERROR_UNION, FE_TYPE_OPTIONAL,
FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_CHAR, FE_TYPE_INT,
FE_TYPE_STRUCT, FE_TYPE_ENUM, FE_TYPE_ARRAY, FE_TYPE_SLICE, FE_TYPE_STR,
FE_TYPE_REF, FE_TYPE_OWNED,
/* `*T`. A machine address and nothing else: no borrow to track, no drop
to run, Copy. Everything it is good for is behind `unsafe`. */
FE_TYPE_RAW,
FE_TYPE_UNKNOWN
} FeTypeKind;
/* One target, one pointer width (SPEC 2). usize and isize are that width and
are not promised to be any particular number of bits, which is what keeps a
different width possible later. */
#define FE_PTR_SIZE 4UL
#define FE_PTR_ALIGN 4U
#define FE_PTR_BITS 32U
typedef struct FeFieldType FeFieldType;
/* A type parameter bound to an argument while an instance is checked. */
#define FE_TYPE_PARAM_MAX 8
typedef struct FeTypeBind {
const char *name;
FeType *type;
} FeTypeBind;
typedef struct FeVariantType FeVariantType;
struct FeFieldType {
char *name;
FeType *type;
unsigned long offset;
const FeNode *ast_node;
};
struct FeVariantType {
char *name;
FeFieldType *fields;
unsigned field_count;
unsigned tag;
const FeNode *ast_node;
char *maker;
};
struct FeType {
FeTypeKind kind;
/* Long enough for a nested instance spelling such as
`Box(Box(Box(i32)))` at the depth limit. */
char name[256];
/* The unit that declared this type, for the nominal kinds. NULL for
builtins and for structural types like `[]u8`, which every unit
shares. Two units declaring the same name declare two types. */
const char *unit;
char *cname;
char *maker;
char *none_cname;
char *unwrap_cname;
char *indexer;
char *slicer;
char *full_slicer;
char *tail_slicer;
char *drop_cname;
char *alloc_cname;
char *replace_cname;
unsigned bits;
int is_unsigned;
int packed;
int is_error;
int has_drop;
unsigned long length;
unsigned long size;
unsigned align;
/* Element/payload type for refs, owners, slices and optionals. For an
error union this is the nominal error identity; NULL means core.Error. */
FeType *elem;
/* Success value for an error union. */
FeType *error_value;
int ref_mut;
FeFieldType *fields;
unsigned field_count;
FeVariantType *variants;
unsigned variant_count;
/* The declaration this type came from, and the bindings that made it if
it is a generic instance. A method has to be checked with the same
bindings the instance was built with. */
/* A small unique number, used to name an instance whose readable
spelling would be too long to keep distinct. */
unsigned serial;
const FeNode *decl_node;
FeTypeBind binds[FE_TYPE_PARAM_MAX];
unsigned bind_count;
FeType *next;
int emit_state;
int cycle_state;
/* Separate from `cycle_state`: the checker's by-value recursion walk and
this layer's size computation run inside one another, and sharing one
marker made a struct in the middle of the first look complete to the
second -- one byte wide, with every field on top of the next. */
int layout_state;
/* Set while a generic instance is being filled in. Its field array
exists but says nothing yet, and a size taken from it would be
wrong and would then be frozen. */
int building;
};
typedef struct FeTypeCtx {
FeArena *arena;
FeType *types;
unsigned pointer_bits;
const char *unit_name;
unsigned generated_serial;
/* Bindings in force right now. A name that is a bound parameter is
that argument's type and nothing else. */
FeTypeBind params[FE_TYPE_PARAM_MAX];
unsigned param_count;
/* Instantiate `Name(args...)`. Only the checker knows the declarations,
so it installs this and the type layer calls back into it. */
FeType *(*instantiate)(void *owner, const FeNode *node);
void *instantiate_owner;
/* A field type is written in the unit that declared it, so resolving one
has to happen with that unit's imports in scope. The checker owns that
knowledge, so it installs this pair and the type layer calls back.
`enter` answers with what to hand `leave`, or -1 for "stayed put". */
int (*enter_decl)(void *owner, const char *unit);
void (*leave_decl)(void *owner, int back);
} FeTypeCtx;
void fe_types_init(FeTypeCtx *ctx, FeArena *arena, unsigned pointer_bits);
FeType *fe_type_intern(FeTypeCtx *ctx, const char *name);
/* Intern a nominal type belonging to `unit` rather than to whichever unit
is being checked. Used to name a type across a unit boundary. */
FeType *fe_type_intern_unit(FeTypeCtx *ctx, const char *unit,
const char *name);
FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node);
FeType *fe_type_array(FeTypeCtx *ctx, unsigned long length, FeType *elem);
FeType *fe_type_slice(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_mut_slice(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable);
FeType *fe_type_owned(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_raw(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_error_union(FeTypeCtx *ctx, FeType *value);
void fe_type_require_replace(FeTypeCtx *ctx, FeType *type);
FeType *fe_type_declare_struct(FeTypeCtx *ctx, const FeNode *node, int packed);
FeType *fe_type_declare_enum(FeTypeCtx *ctx, const FeNode *node);
FeType *fe_type_declare_error(FeTypeCtx *ctx, const FeNode *node);
void fe_type_layout_all(FeTypeCtx *ctx);
FeFieldType *fe_type_field(FeType *t, const char *name);
FeVariantType *fe_type_variant(FeType *t, const char *name);
int fe_type_equal(const FeType *a, const FeType *b);
int fe_type_is_integer(const FeType *t);
int fe_type_is_indexable(const FeType *t);
const char *fe_type_c_name(const FeType *t, unsigned pointer_bits);
unsigned long fe_type_size(const FeType *t);
/* Where the payload of an optional or an error union sits. The tag comes
first and the value is aligned after it; both the layout pass and the
code generator have to agree, so the rule lives in one place. */
unsigned long fe_type_payload_offset(const FeType *t);
unsigned fe_type_align(const FeType *t);
#endif
+677
View File
@@ -0,0 +1,677 @@
#include "x86.h"
#include <string.h>
#include <stdlib.h>
/* ------------------------------------------------------------------------- *
* i386 code generation
*
* The frame, from EBP downwards:
*
* [ebp + 8 + 4k] incoming argument k
* [ebp + 4] return address
* [ebp] saved ebp
* [ebp - ...] parameters, copied in from the argument area
* [ebp - ...] locals
* [ebp - ...] one slot per temporary
*
* Parameters are copied into the frame rather than read in place so that a
* parameter and a local are the same thing to everything below.
* ------------------------------------------------------------------------- */
/* Which register a temporary lives in, or none. Only ebx, esi and edi are
handed out: eax, ecx and edx are the scratch this emitter computes in, and
the three that are left survive a call without being saved. */
#define REG_NONE 0
#define REG_COUNT 3
static const char *const REGS[REG_COUNT] = { "ebx", "esi", "edi" };
typedef struct Frame {
const FeIrFunc *f;
long *local_off; /* [ebp + off] for each local */
long temp_base; /* first temporary slot */
long size; /* bytes to subtract from esp */
/* 0 means the temporary lives in its stack slot. */
unsigned char *temp_reg;
} Frame;
static long align_up(long v, long a)
{
long r = v % a;
return r ? v + a - r : v;
}
static unsigned long slot_bytes(const FeIrLocal *l)
{
switch (l->type) {
case FE_IR_I8: return 1;
case FE_IR_I16: return 2;
case FE_IR_I32: return 4;
case FE_IR_PTR: return 4;
case FE_IR_MEM: return l->size ? l->size : 1;
default: return 4;
}
}
/* Every temporary is four bytes: a temporary only ever holds something that
fits in a register, and narrower values are kept zero- or sign-extended. */
#define TEMP_SLOT 4L
static void frame_layout(Frame *fr, const FeIrFunc *f, long *storage)
{
unsigned i;
long off = 0;
fr->f = f;
fr->local_off = storage;
for (i = 0; i < f->local_count; ++i) {
unsigned long size = slot_bytes(&f->locals[i]);
long a = (long)f->locals[i].align;
if (a < 1) a = 1;
if (a > 4) a = 4;
off = align_up(off + (long)size, a);
storage[i] = -off;
}
off = align_up(off, 4);
fr->temp_base = -off;
off += (long)f->temp_count * TEMP_SLOT;
fr->size = align_up(off, 4);
}
/* Which temporaries a value reads. Returns how many it wrote into `used`. */
static unsigned reads_of(const FeIrValue *v, unsigned *used)
{
unsigned n = 0;
unsigned i;
switch (v->op) {
case FE_IR_CONST: break;
case FE_IR_LOAD:
case FE_IR_ADDR:
if (v->place.base == FE_PLACE_TEMP) used[n++] = v->place.index;
break;
case FE_IR_STORE:
if (v->place.base == FE_PLACE_TEMP) used[n++] = v->place.index;
used[n++] = v->a;
break;
case FE_IR_COPY:
if (v->place.base == FE_PLACE_TEMP) used[n++] = v->place.index;
if (v->place2.base == FE_PLACE_TEMP) used[n++] = v->place2.index;
break;
case FE_IR_CAST:
used[n++] = v->a;
break;
case FE_IR_CALL:
for (i = 0; i < v->arg_count && n < 18; ++i) used[n++] = v->args[i];
break;
default:
used[n++] = v->a;
used[n++] = v->b;
break;
}
return n;
}
/* Give registers to the temporaries that can hold one.
A temporary that is defined in one block and read in another has to go
through memory: this walks one block at a time and knows nothing about the
others. Lowering does produce such temporaries -- a bounds check splits a
block between computing an index and using it -- so eligibility is decided
over the whole function first, and the scan inside a block only considers
what survived that. */
static void allocate_registers(Frame *fr, const FeIrFunc *f)
{
unsigned n = f->temp_count;
unsigned char *single; /* 1 while the temporary stays in one block */
unsigned *home; /* the block it was defined in */
unsigned *last; /* the last instruction in that block to read it */
const FeIrBlock *b;
const FeIrValue *v;
unsigned used[20];
unsigned i, k, at;
if (!n) { fr->temp_reg = 0; return; }
fr->temp_reg = (unsigned char *)calloc(n, 1);
single = (unsigned char *)calloc(n, 1);
home = (unsigned *)calloc(n, sizeof(unsigned));
last = (unsigned *)calloc(n, sizeof(unsigned));
if (!fr->temp_reg || !single || !home || !last) {
free(single); free(home); free(last);
return;
}
for (i = 0; i < n; ++i) { single[i] = 1; home[i] = 0xFFFFFFFFU; }
for (b = f->first; b; b = b->next) {
for (v = b->first; v; v = v->next) {
if (v->has_dest) {
if (home[v->dest] != 0xFFFFFFFFU) single[v->dest] = 0;
home[v->dest] = b->id;
}
k = reads_of(v, used);
for (i = 0; i < k; ++i)
if (used[i] < n && home[used[i]] != b->id) single[used[i]] = 0;
}
if (b->term == FE_IR_BR && b->cond < n && home[b->cond] != b->id)
single[b->cond] = 0;
if (b->term == FE_IR_RET && b->has_ret_value && b->ret_value < n &&
home[b->ret_value] != b->id)
single[b->ret_value] = 0;
}
for (b = f->first; b; b = b->next) {
unsigned char busy[REG_COUNT];
unsigned owner[REG_COUNT];
for (i = 0; i < REG_COUNT; ++i) { busy[i] = 0; owner[i] = 0; }
/* When each temporary is last read in this block. */
at = 0;
for (v = b->first; v; v = v->next, ++at) {
k = reads_of(v, used);
for (i = 0; i < k; ++i)
if (used[i] < n && single[used[i]]) last[used[i]] = at;
}
if (b->term == FE_IR_BR && b->cond < n && single[b->cond])
last[b->cond] = at;
if (b->term == FE_IR_RET && b->has_ret_value && b->ret_value < n &&
single[b->ret_value]) last[b->ret_value] = at;
at = 0;
for (v = b->first; v; v = v->next, ++at) {
/* Free whatever was read for the last time before this. */
for (i = 0; i < REG_COUNT; ++i)
if (busy[i] && last[owner[i]] < at) busy[i] = 0;
if (!v->has_dest || !single[v->dest]) continue;
/* A call clobbers the scratch registers but not these three, so a
result can still be kept in one across the call that made it. */
for (i = 0; i < REG_COUNT; ++i)
if (!busy[i]) {
busy[i] = 1;
owner[i] = v->dest;
fr->temp_reg[v->dest] = (unsigned char)(i + 1);
break;
}
}
}
free(single); free(home); free(last);
}
static long temp_off(const Frame *fr, unsigned t)
{
return fr->temp_base - (long)(t + 1) * TEMP_SLOT;
}
static const char *word_of(FeIrType t)
{
switch (t) {
case FE_IR_I8: return "byte ptr";
case FE_IR_I16: return "word ptr";
default: return "dword ptr";
}
}
static const char *reg_of(FeIrType t, int which)
{
/* which: 0 -> a, 1 -> c, 2 -> d */
switch (t) {
case FE_IR_I8: return which == 0 ? "al" : which == 1 ? "cl" : "dl";
case FE_IR_I16: return which == 0 ? "ax" : which == 1 ? "cx" : "dx";
default: return which == 0 ? "eax" : which == 1 ? "ecx" : "edx";
}
}
/* Write the effective address of a place into `buf`. A place is a base plus a
constant, and the only base that is not already an address is a temporary,
which holds a pointer. */
static void place_addr(const Frame *fr, const FeIrPlace *p, char *buf)
{
switch (p->base) {
case FE_PLACE_LOCAL:
sprintf(buf, "[ebp%+ld]", fr->local_off[p->index] + p->offset);
break;
case FE_PLACE_GLOBAL:
if (p->offset) sprintf(buf, "[%s%+ld]", p->name, p->offset);
else sprintf(buf, "[%s]", p->name);
break;
case FE_PLACE_TEMP:
sprintf(buf, "[edx%+ld]", p->offset);
break;
}
}
/* A temporary-based place needs its pointer in a register first. */
static void load_temp(const Frame *fr, unsigned t, const char *reg,
FILE *out);
static void load_place_base(const Frame *fr, const FeIrPlace *p, FILE *out)
{
if (p->base != FE_PLACE_TEMP) return;
/* Through load_temp, not straight from the slot: the pointer may be living
in a register, in which case the slot was never written. */
load_temp(fr, p->index, "edx", out);
}
static void load_temp(const Frame *fr, unsigned t, const char *reg, FILE *out)
{
if (fr->temp_reg && fr->temp_reg[t]) {
const char *from = REGS[fr->temp_reg[t] - 1];
if (strcmp(from, reg) != 0)
fprintf(out, " mov %s, %s\n", reg, from);
return;
}
fprintf(out, " mov %s, [ebp%+ld]\n", reg, temp_off(fr, t));
}
static void store_temp(const Frame *fr, unsigned t, const char *reg, FILE *out)
{
if (fr->temp_reg && fr->temp_reg[t]) {
const char *to = REGS[fr->temp_reg[t] - 1];
if (strcmp(to, reg) != 0)
fprintf(out, " mov %s, %s\n", to, reg);
return;
}
fprintf(out, " mov [ebp%+ld], %s\n", temp_off(fr, t), reg);
}
/* The register a temporary lives in, or null when it lives in its slot. */
static const char *reg_home(const Frame *fr, unsigned t)
{
if (!fr->temp_reg || !fr->temp_reg[t]) return 0;
return REGS[fr->temp_reg[t] - 1];
}
/* Something an instruction can take as its right-hand operand: a register, or
the temporary's slot read in place. */
static void operand_of(const Frame *fr, unsigned t, char *buf)
{
const char *r = reg_home(fr, t);
if (r) strcpy(buf, r);
else sprintf(buf, "dword ptr [ebp%+ld]", temp_off(fr, t));
}
static const char *simple_op(FeIrOp op)
{
switch (op) {
case FE_IR_ADD: return "add ";
case FE_IR_SUB: return "sub ";
case FE_IR_AND: return "and ";
case FE_IR_OR: return "or ";
case FE_IR_XOR: return "xor ";
case FE_IR_MUL: return "imul";
default: return 0;
}
}
static const char *cmp_set(FeIrOp op, int is_unsigned)
{
switch (op) {
case FE_IR_EQ: return "sete";
case FE_IR_NE: return "setne";
case FE_IR_LT: return is_unsigned ? "setb" : "setl";
case FE_IR_LE: return is_unsigned ? "setbe" : "setle";
case FE_IR_GT: return is_unsigned ? "seta" : "setg";
case FE_IR_GE: return is_unsigned ? "setae" : "setge";
default: return "sete";
}
}
static void emit_binary(const Frame *fr, const FeIrValue *v, FILE *out)
{
int is_cmp = v->op >= FE_IR_EQ && v->op <= FE_IR_GE;
FeIrType t = is_cmp ? (FeIrType)v->imm : v->type;
const char *a = reg_of(t, 0);
const char *c = reg_of(t, 1);
/* When the result has a register of its own and the operation is one that
can work on any register, the whole thing happens there: no trip through
the scratch register and no trip through memory.
Only the full-width operations qualify. esi and edi have no byte halves,
so a narrow operation still goes through eax, where they do. */
if (!is_cmp && v->has_dest && (t == FE_IR_I32 || t == FE_IR_PTR) &&
simple_op(v->op)) {
const char *d = reg_home(fr, v->dest);
const char *rb = reg_home(fr, v->b);
if (d && !(rb && strcmp(rb, d) == 0)) {
char right[64];
load_temp(fr, v->a, d, out);
operand_of(fr, v->b, right);
fprintf(out, " %s %s, %s\n", simple_op(v->op), d, right);
return;
}
}
/* A full-width comparison can read both sides where they already are; the
answer still has to come out of `al`, which is why it lands in eax when
the result has no register of its own. */
if (is_cmp && (t == FE_IR_I32 || t == FE_IR_PTR)) {
const char *left = reg_home(fr, v->a);
const char *d = reg_home(fr, v->dest);
char right[64];
if (!left) { load_temp(fr, v->a, "eax", out); left = "eax"; }
operand_of(fr, v->b, right);
fprintf(out, " cmp %s, %s\n", left, right);
fprintf(out, " %s al\n", cmp_set(v->op, v->is_unsigned));
fprintf(out, " movzx %s, al\n", d ? d : "eax");
if (!d) store_temp(fr, v->dest, "eax", out);
return;
}
load_temp(fr, v->a, "eax", out);
load_temp(fr, v->b, "ecx", out);
if (is_cmp) {
fprintf(out, " cmp %s, %s\n", a, c);
fprintf(out, " %s al\n", cmp_set(v->op, v->is_unsigned));
fprintf(out, " movzx eax, al\n");
store_temp(fr, v->dest, "eax", out);
return;
}
switch (v->op) {
case FE_IR_ADD: fprintf(out, " add %s, %s\n", a, c); break;
case FE_IR_SUB: fprintf(out, " sub %s, %s\n", a, c); break;
case FE_IR_MUL: fprintf(out, " imul %s, %s\n", a, c); break;
case FE_IR_AND: fprintf(out, " and %s, %s\n", a, c); break;
case FE_IR_OR: fprintf(out, " or %s, %s\n", a, c); break;
case FE_IR_XOR: fprintf(out, " xor %s, %s\n", a, c); break;
case FE_IR_SHL: fprintf(out, " shl %s, cl\n", a); break;
case FE_IR_SHR:
fprintf(out, " %s %s, cl\n",
v->is_unsigned ? "shr" : "sar", a);
break;
case FE_IR_DIV:
case FE_IR_MOD:
/* The divide instructions use edx:eax, so the operands have to be
widened to 32 bits whatever the declared width is. */
if (v->is_unsigned) fprintf(out, " xor edx, edx\n");
else fprintf(out, " cdq\n");
fprintf(out, " %s ecx\n", v->is_unsigned ? "div " : "idiv");
if (v->op == FE_IR_MOD) fprintf(out, " mov eax, edx\n");
break;
default: break;
}
store_temp(fr, v->dest, "eax", out);
}
static void emit_value(const Frame *fr, const FeIrValue *v, FILE *out)
{
char addr[128];
unsigned i;
switch (v->op) {
case FE_IR_CONST: {
const char *d = reg_home(fr, v->dest);
fprintf(out, " mov %s, %ld\n", d ? d : "eax", v->imm);
if (!d) store_temp(fr, v->dest, "eax", out);
break;
}
case FE_IR_LOAD: {
const char *d = reg_home(fr, v->dest);
const char *into = d ? d : "eax";
load_place_base(fr, &v->place, out);
place_addr(fr, &v->place, addr);
if (v->type == FE_IR_I8)
fprintf(out, " movzx %s, byte ptr %s\n", into, addr);
else if (v->type == FE_IR_I16)
fprintf(out, " movzx %s, word ptr %s\n", into, addr);
else
fprintf(out, " mov %s, dword ptr %s\n", into, addr);
if (!d) store_temp(fr, v->dest, "eax", out);
break;
}
case FE_IR_STORE: {
const char *from = reg_home(fr, v->a);
load_place_base(fr, &v->place, out);
place_addr(fr, &v->place, addr);
/* A full-width value already in a register goes straight out; a narrow
one needs a byte or word half, which only eax has here. */
if (from && (v->type == FE_IR_I32 || v->type == FE_IR_PTR)) {
fprintf(out, " mov %s %s, %s\n", word_of(v->type), addr,
from);
break;
}
load_temp(fr, v->a, "eax", out);
fprintf(out, " mov %s %s, %s\n", word_of(v->type), addr,
reg_of(v->type, 0));
break;
}
case FE_IR_ADDR: {
const char *d = reg_home(fr, v->dest);
load_place_base(fr, &v->place, out);
place_addr(fr, &v->place, addr);
fprintf(out, " lea %s, %s\n", d ? d : "eax", addr);
if (!d) store_temp(fr, v->dest, "eax", out);
break;
}
case FE_IR_CAST:
load_temp(fr, v->a, "eax", out);
/* Narrowing is free once everything is kept in a 32-bit slot; widening
has to say whether the top bits are copies of the sign. */
if (v->type == FE_IR_I8)
fprintf(out, " %s eax, al\n",
v->is_unsigned ? "movzx" : "movsx");
else if (v->type == FE_IR_I16)
fprintf(out, " %s eax, ax\n",
v->is_unsigned ? "movzx" : "movsx");
store_temp(fr, v->dest, "eax", out);
break;
case FE_IR_CALL:
/* cdecl: arguments pushed right to left, the caller pops them. */
for (i = v->arg_count; i > 0; --i) {
load_temp(fr, v->args[i - 1], "eax", out);
fprintf(out, " push eax\n");
}
fprintf(out, " call %s\n", v->callee);
if (v->arg_count)
fprintf(out, " add esp, %u\n", v->arg_count * 4U);
if (v->has_dest) store_temp(fr, v->dest, "eax", out);
break;
case FE_IR_COPY: {
char dst[128];
char src[128];
/* Both addresses are worked out in the scratch registers first, and
only then does the block copy take over esi and edi -- which may be
holding temporaries, so it hands them back. */
if (v->place2.base == FE_PLACE_TEMP) {
load_temp(fr, v->place2.index, "eax", out);
if (v->place2.offset)
fprintf(out, " add eax, %ld\n", v->place2.offset);
} else {
place_addr(fr, &v->place2, src);
fprintf(out, " lea eax, %s\n", src);
}
if (v->place.base == FE_PLACE_TEMP) {
load_temp(fr, v->place.index, "edx", out);
if (v->place.offset)
fprintf(out, " add edx, %ld\n", v->place.offset);
} else {
place_addr(fr, &v->place, dst);
fprintf(out, " lea edx, %s\n", dst);
}
fprintf(out, " push esi\n");
fprintf(out, " push edi\n");
fprintf(out, " mov esi, eax\n");
fprintf(out, " mov edi, edx\n");
fprintf(out, " mov ecx, %ld\n", v->imm);
fprintf(out, " cld\n");
fprintf(out, " rep movsb\n");
fprintf(out, " pop edi\n pop esi\n");
break;
}
default:
emit_binary(fr, v, out);
break;
}
}
static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out)
{
Frame fr;
long *storage;
const FeIrBlock *b;
const FeIrValue *v;
unsigned i;
long arg = 8;
if (f->is_extern || !f->first) return;
/* One offset per local, however many there are. A fixed array here would
silently stop emitting a function that had too many. */
storage = (long *)malloc((size_t)(f->local_count ? f->local_count : 1) *
sizeof(long));
if (!storage) return;
frame_layout(&fr, f, storage);
allocate_registers(&fr, f);
fprintf(out, "\npublic %s\n", f->name);
fprintf(out, "%s proc near\n", f->name);
fprintf(out, " push ebp\n");
fprintf(out, " mov ebp, esp\n");
if (fr.size) fprintf(out, " sub esp, %ld\n", fr.size);
fprintf(out, " push ebx\n push esi\n"
" push edi\n");
/* Copy the incoming arguments into the frame. */
for (i = 0; i < f->param_count; ++i) {
fprintf(out, " mov eax, [ebp+%ld]\n", arg);
fprintf(out, " mov %s [ebp%+ld], %s\n",
word_of(f->locals[i].type), storage[i],
reg_of(f->locals[i].type, 0));
arg += 4;
}
for (b = f->first; b; b = b->next) {
fprintf(out, "L%s_%u:\n", f->name, b->id);
for (v = b->first; v; v = v->next) emit_value(&fr, v, out);
switch (b->term) {
case FE_IR_JMP:
fprintf(out, " jmp L%s_%u\n", f->name, b->target);
break;
case FE_IR_BR:
load_temp(&fr, b->cond, "eax", out);
fprintf(out, " test eax, eax\n");
fprintf(out, " jnz L%s_%u\n", f->name, b->target);
fprintf(out, " jmp L%s_%u\n", f->name, b->target_else);
break;
case FE_IR_RET:
if (b->has_ret_value) load_temp(&fr, b->ret_value, "eax", out);
fprintf(out, " pop edi\n pop esi\n"
" pop ebx\n");
fprintf(out, " mov esp, ebp\n pop ebp\n");
fprintf(out, " ret\n");
break;
case FE_IR_TRAP:
fprintf(out, " push %lu\n", b->trap_line);
fprintf(out, " push offset FE_FILE_%u\n",
b->trap_file);
fprintf(out, " push %u\n", (unsigned)b->trap);
fprintf(out, " call fe_trap\n");
fprintf(out, " add esp, 12\n");
break;
}
}
fprintf(out, "%s endp\n", f->name);
free(storage);
free(fr.temp_reg);
(void)m;
}
static void emit_string(const char *s, FILE *out)
{
int in = 0;
fputs(" db ", out);
for (; s && *s; ++s) {
unsigned char c = (unsigned char)*s;
if (c >= 32 && c < 127 && c != '\'' && c != '"') {
if (!in) { fputc('\'', out); in = 1; }
fputc(c, out);
} else {
if (in) { fputs("',", out); in = 0; }
fprintf(out, "%u,", c);
}
}
if (in) fputc('\'', out);
else fputc('0', out);
if (in) fputs(",0", out);
fputc('\n', out);
}
void fe_x86_emit(const FeIrModule *m, FILE *out)
{
const FeIrFunc *f;
const FeIrGlobal *g;
int any_trap = 0;
const FeIrBlock *b;
unsigned i;
for (f = m->funcs; f && !any_trap; f = f->next)
for (b = f->first; b; b = b->next)
if (b->term == FE_IR_TRAP) { any_trap = 1; break; }
fputs(".386\n.model flat\n\n", out);
for (f = m->funcs; f; f = f->next)
if (f->is_extern || !f->first)
fprintf(out, "extern %s : near\n", f->name);
/* Anything called but not defined here lives somewhere else -- the runtime,
or a library. Lowering emits such calls directly (allocating, writing,
trapping), so the names are collected from the calls themselves rather
than from a list that would have to be kept in step. */
{
const char *seen[64];
unsigned count = 0;
const FeIrValue *v;
const FeIrFunc *g;
unsigned i;
for (f = m->funcs; f; f = f->next)
for (b = f->first; b; b = b->next)
for (v = b->first; v; v = v->next) {
if (v->op != FE_IR_CALL || !v->callee) continue;
for (g = m->funcs; g; g = g->next)
if (!strcmp(g->name, v->callee)) break;
if (g) continue;
for (i = 0; i < count; ++i)
if (!strcmp(seen[i], v->callee)) break;
if (i < count || count >= 64) continue;
seen[count++] = v->callee;
fprintf(out, "extern %s : near\n", v->callee);
}
}
if (any_trap) fputs("extern fe_trap : near\n", out);
fputs("\n_DATA segment dword public 'DATA'\n", out);
/* One name per file a trap can come from. A build is many units in
one module, and a trap that names the wrong file is worse than
one that names none. */
for (i = 0; i < m->file_count; ++i) {
fprintf(out, "public FE_FILE_%u\nFE_FILE_%u label byte\n", i, i);
emit_string(m->files[i], out);
}
for (g = m->globals; g; g = g->next) {
unsigned long i;
fprintf(out, "public %s\n%s label byte\n", g->name, g->name);
if (!g->init) {
fprintf(out, " db %lu dup(0)\n", g->size ? g->size : 1UL);
continue;
}
for (i = 0; i < g->size; ) {
unsigned r;
unsigned long j;
for (r = 0; r < g->reloc_count; ++r)
if (g->relocs[r].at == i) break;
if (r < g->reloc_count) {
/* A hole the linker fills with an address. */
fprintf(out, " dd offset %s\n",
g->relocs[r].symbol);
i += 4;
continue;
}
fputs(" db ", out);
j = 0;
while (i < g->size && j < 16) {
unsigned q;
for (q = 0; q < g->reloc_count; ++q)
if (g->relocs[q].at == i) break;
if (q < g->reloc_count) break;
fprintf(out, "%s%u", j ? "," : "", g->init[i]);
++i; ++j;
}
fputc('\n', out);
}
if (!g->size) fputs(" db 0\n", out);
}
fputs("_DATA ends\n", out);
fputs("\n_TEXT segment dword public 'CODE'\n", out);
for (f = m->funcs; f; f = f->next) emit_func(m, f, out);
/* The runtime's entry stub calls one fixed name, so point it here. */
if (m->entry_main)
fprintf(out, "\npublic fe_main_\nfe_main_ proc near\n"
" jmp %s\nfe_main_ endp\n", m->entry_main);
fputs("\n_TEXT ends\n\nend\n", out);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef FE_X86_H
#define FE_X86_H
#include "ir.h"
/* IR to i386 assembly, in the syntax Open Watcom's `wasm` accepts.
There is no register allocator. Every temporary gets a stack slot, and every
instruction loads its operands into fixed registers, computes, and stores
the result back. That is slow code and obviously correct code, and correct
comes first: a register allocator can be dropped in later without the rest
of the compiler noticing, because it only changes where a temporary lives. */
void fe_x86_emit(const FeIrModule *m, FILE *out);
#endif
+166
View File
@@ -0,0 +1,166 @@
unit std.arena;
import std.mem;
// An arena of `T` reached by handle, and the handle that reaches it.
//
// This is the shape SPEC R11 asks for when data points at data: the arena owns
// the values and a handle is a number, so nothing here is a reference and R4
// has nothing to object to. `mem.Arena` next door is a different structure --
// a block of bytes handed out by offset, for data whose size varies. This one
// holds a fixed `T` per slot and can take slots back.
//
// A handle is checked, not trusted. It carries the generation of the slot it
// was made from, the epoch of the arena, and which arena it came from, so
// using it after the slot was freed, after the arena was reset, or against
// some other arena all answer `null` rather than a neighbouring value.
//
// `T` is expected to be Copy -- integers, handles, small records. `get`
// answers with a copy, which is the whole reason a borrow never outlives the
// statement it was taken in. For a `T` that owns something, `take` moves it
// out and leaves a replacement (SPEC 5 R7).
/// No slot. Every real index is smaller.
pub const NONE: u32 = 4294967295;
/// Eight bytes, and the same eight in every build: `--no-checks` skips the
/// comparison, never the layout. A handle that changed shape with a flag
/// would not survive being written down.
pub struct Handle(T) {
pub index: u32,
/// `arena 8 | epoch 8 | generation 16`
pub tag: u32,
pub fn none() -> Self { return Self{ index: NONE, tag: 0 }; }
pub fn is_none(self: &Self) -> bool { return self.index == NONE; }
pub fn same(self: &Self, other: Handle(T)) -> bool {
return self.index == other.index and self.tag == other.tag;
}
}
struct Slot(T) {
value: T,
/// Bumped every time the slot is freed, so old handles stop matching.
gen: u32,
live: bool,
/// The next slot on the free list, or `NONE`.
next: u32,
}
pub struct Arena(T) {
slots: ^[]mut Slot(T),
/// How many slots have ever been handed out; slots past this are untouched.
high: usize,
/// How many are live right now.
count: usize,
free: u32,
id: u32,
epoch: u32,
/// `id` tells one arena from another in a handle. A program with a handful
/// of arenas numbers them itself; eight bits is more than that needs.
pub fn with_capacity(id: u32, n: usize) -> !Self {
let room: ^[]mut Slot(T) = try mem.alloc_slice(Slot(T), n);
return Self{ slots: room, high: 0, count: 0, free: NONE,
id: id % 256, epoch: 0 };
}
pub fn len(self: &Self) -> usize { return self.count; }
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
fn tag_of(self: &Self, gen: u32) -> u32 {
return (self.id * 16777216) + (self.epoch * 65536) + gen;
}
/// Is this handle still talking about a live slot in this arena?
pub fn valid(self: &Self, h: Handle(T)) -> bool {
if h.index == NONE { return false; }
if (h.index as usize) >= self.high { return false; }
if not self.slots.^[h.index as usize].live { return false; }
return self.slots.^[h.index as usize].gen == h.tag;
}
pub fn alloc(self: &mut Self, v: T) -> !Handle(T) {
var at: usize = 0;
if self.free != NONE {
at = self.free as usize;
self.free = self.slots.^[at].next;
} else {
if self.high == self.slots.^.n { return error.OutOfMemory; }
at = self.high;
self.high = self.high + 1;
self.slots.^[at].gen = self.tag_of(0);
}
self.slots.^[at].value = v;
self.slots.^[at].live = true;
self.slots.^[at].next = NONE;
self.count = self.count + 1;
return Handle(T){ index: at as u32, tag: self.slots.^[at].gen };
}
/// A copy of what the slot holds, or nothing when the handle is stale.
/// The borrow of the arena ends with this statement, which is what lets a
/// caller read one slot while writing another.
pub fn get(self: &Self, h: Handle(T)) -> ?T {
if not self.valid(h) { return null; }
return self.slots.^[h.index as usize].value;
}
/// Overwrite in place. Says whether the handle was good.
pub fn set(self: &mut Self, h: Handle(T), v: T) -> bool {
if not self.valid(h) { return false; }
self.slots.^[h.index as usize].value = v;
return true;
}
/// Move the value out and leave `replacement` behind (SPEC 5 R7). This is
/// how a `T` that owns something leaves the arena.
pub fn take(self: &mut Self, h: Handle(T), replacement: T) -> ?T {
if not self.valid(h) { return null; }
return mem.replace(&mut self.slots.^[h.index as usize].value,
replacement);
}
/// Exchange what two slots hold. Both handles have to be good.
pub fn swap(self: &mut Self, a: Handle(T), b: Handle(T)) -> bool {
if not self.valid(a) { return false; }
if not self.valid(b) { return false; }
let ai: usize = a.index as usize;
let bi: usize = b.index as usize;
if ai == bi { return true; }
let first: T = self.slots.^[ai].value;
let second: T = mem.replace(&mut self.slots.^[bi].value, first);
self.slots.^[ai].value = second;
return true;
}
/// Give the slot back. Every handle to it stops matching. A slot whose
/// generation has run out is retired rather than reused -- wrapping around
/// would make an old handle valid again, which is the one thing the
/// generation is there to prevent.
pub fn free(self: &mut Self, h: Handle(T)) -> bool {
if not self.valid(h) { return false; }
let at: usize = h.index as usize;
self.slots.^[at].live = false;
self.count = self.count - 1;
let gen: u32 = self.slots.^[at].gen % 65536;
if gen == 65535 { return true; }
self.slots.^[at].gen = self.slots.^[at].gen + 1;
self.slots.^[at].next = self.free;
self.free = h.index;
return true;
}
/// Forget everything at once. The epoch moves, so every handle made before
/// now is stale without having to touch a single slot.
pub fn reset(self: &mut Self) -> void {
self.epoch = (self.epoch + 1) % 256;
self.high = 0;
self.count = 0;
self.free = NONE;
return;
}
}
+12
View File
@@ -0,0 +1,12 @@
unit std.core;
// The default error set is open: `error.Name` names a member of it without
// declaring one, and the build assigns the codes (SPEC 4.6).
pub fn assert(ok: bool) -> void {
if not ok { @trap(); }
}
pub fn min(a: i32, b: i32) -> i32 { if a < b { return a; } return b; }
pub fn max(a: i32, b: i32) -> i32 { if a > b { return a; } return b; }
pub fn abs(v: i32) -> i32 { if v < 0 { return 0 - v; } return v; }
+32
View File
@@ -0,0 +1,32 @@
unit std.fmt;
// Pure conversion. Nothing here owns a sink: the caller supplies the buffer
// and is told how many bytes at the front of it were written (SPEC 10).
pub fn fmt_u32(buf: []mut u8, v: u32) -> usize {
var tmp: [10]u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var value: u32 = v;
var count: usize = 0;
while true {
tmp[count] = ((value % 10) as u8) + ('0' as u8);
count = count + 1;
value = value / 10;
if value == 0 { break; }
if count == 10 { break; }
}
var i: usize = 0;
while i < count {
if i < buf.n { buf[i] = tmp[count - 1 - i]; }
i = i + 1;
}
return count;
}
pub fn fmt_i32(buf: []mut u8, v: i32) -> usize {
if v >= 0 { return fmt_u32(buf, v as u32); }
if buf.n == 0 { return 0; }
buf[0] = '-' as u8;
var rest: []mut u8 = buf[1..buf.n];
let digits: usize = fmt_u32(rest, (0 - v) as u32);
return digits + 1;
}
+142
View File
@@ -0,0 +1,142 @@
unit std.intern;
import std.map;
import std.mem;
// One copy of every distinct name, and a number that stands for it.
//
// A compiler compares names constantly and stores them everywhere. Comparing
// two `StrId` is comparing two integers; storing one costs four bytes and no
// ownership. That is the whole point.
//
// There is deliberately no way to get a `str` back out. A borrow of the text
// would be a borrow of the interner, and the interner is exactly the thing you
// want to keep adding to while holding names -- the parser reads an identifier
// and registers the next one in the same breath. Everything you would open the
// text for is here instead: compare it, measure it, hash it, write it.
/// A name, as a number. Copy, four bytes, and meaningless to any other
/// interner -- which is fine, because a program has one.
pub struct StrId {
pub raw: u32,
pub fn same(self: &Self, other: StrId) -> bool {
return self.raw == other.raw;
}
}
/// No name.
pub const NONE: u32 = 4294967295;
/// A name written out as map key bytes. `std.map` keys on bytes, so a name
/// used as a key is just its number -- four of them, and nothing allocated.
/// A symbol table is `Map(V)` keyed on this.
pub fn key_of(id: StrId, out: []mut u8) -> []u8 {
out[0] = (id.raw % 256) as u8;
out[1] = ((id.raw / 256) % 256) as u8;
out[2] = ((id.raw / 65536) % 256) as u8;
out[3] = ((id.raw / 16777216) % 256) as u8;
return out[0..4];
}
struct Entry {
at: usize,
len: usize,
}
pub struct Interner {
/// Every name end to end. Nothing is ever removed, so an offset stays
/// good for as long as the interner does.
bytes: ^[]mut u8,
used: usize,
names: ^[]mut Entry,
count: usize,
/// Text to id, so interning the same name twice gives the same number.
seen: map.Map(u32),
pub fn with_capacity(n: usize) -> !Self {
let text: ^[]mut u8 = try mem.alloc_slice(u8, n * 8);
let table: ^[]mut Entry = try mem.alloc_slice(Entry, n);
let index: map.Map(u32) = try map.Map(u32).with_capacity(n);
return Self{ bytes: text, used: 0, names: table, count: 0,
seen: index };
}
pub fn count_of(self: &Self) -> usize { return self.count; }
/// The number for this name, making one if it is new.
pub fn intern(self: &mut Self, text: []u8) -> !StrId {
let found: u32 = self.seen.get(text, NONE);
if found != NONE { return StrId{ raw: found }; }
if self.count == self.names.^.n { return error.OutOfMemory; }
let at: usize = self.used;
if at + text.n > self.bytes.^.n { return error.OutOfMemory; }
var i: usize = 0;
while i < text.n {
self.bytes.^[at + i] = text[i];
i = i + 1;
}
let id: usize = self.count;
self.names.^[id].at = at;
self.names.^[id].len = text.n;
self.used = at + text.n;
self.count = id + 1;
try self.seen.put(text, id as u32);
return StrId{ raw: id as u32 };
}
/// Is this a name it has seen? `NONE` when not.
pub fn find(self: &Self, text: []u8) -> u32 {
return self.seen.get(text, NONE);
}
pub fn len_of(self: &Self, id: StrId) -> usize {
if (id.raw as usize) >= self.count { return 0; }
return self.names.^[id.raw as usize].len;
}
/// Does this id spell this text? The comparison every `if name == "fn"`
/// in a parser turns into.
pub fn eq(self: &Self, id: StrId, text: []u8) -> bool {
if (id.raw as usize) >= self.count { return false; }
let e: usize = id.raw as usize;
if self.names.^[e].len != text.n { return false; }
var i: usize = 0;
while i < text.n {
if self.bytes.^[self.names.^[e].at + i] != text[i] { return false; }
i = i + 1;
}
return true;
}
/// FNV-1a over the stored bytes, for anything that wants to bucket names
/// without opening them.
pub fn hash_of(self: &Self, id: StrId) -> u32 {
if (id.raw as usize) >= self.count { return 0; }
let e: usize = id.raw as usize;
var h: u32 = 2166136261;
var i: usize = 0;
while i < self.names.^[e].len {
h = h ^ (self.bytes.^[self.names.^[e].at + i] as u32);
h = h * 16777619;
i = i + 1;
}
return h;
}
/// Copy the name into `out` and say how many bytes it took. This is how a
/// name reaches a diagnostic without the interner being borrowed past the
/// statement.
pub fn copy_into(self: &Self, id: StrId, out: []mut u8) -> usize {
if (id.raw as usize) >= self.count { return 0; }
let e: usize = id.raw as usize;
var n: usize = self.names.^[e].len;
if n > out.n { n = out.n; }
var i: usize = 0;
while i < n {
out[i] = self.bytes.^[self.names.^[e].at + i];
i = i + 1;
}
return n;
}
}
+120
View File
@@ -0,0 +1,120 @@
unit std.io;
import std.sys;
// A writer is a handle and nothing else: an integer the runtime understands.
// It stores no reference and no context pointer, so it is Copy and can be
// passed and returned freely (SPEC 5 R8).
pub enum Writer { Null, Stdout, Stderr }
pub fn write(w: Writer, bytes: []u8) -> usize {
if w == Writer.Null { return bytes.n; }
var handle: i32 = 1;
if w == Writer.Stderr { handle = 2; }
var done: i32 = 0;
unsafe { done = sys.raw_write(handle, @ptr_cast(u8, &bytes[0]), bytes.n); }
if done < 0 { return 0; }
return done as usize;
}
pub fn print(bytes: []u8) -> usize {
return write(Writer.Stdout, bytes);
}
pub fn println(bytes: []u8) -> usize {
let n: usize = write(Writer.Stdout, bytes);
return n + write(Writer.Stdout, "\n");
}
// Files. A handle is what the operating system gave back; -1 means it did not
// give one. The path has to be NUL terminated because that is what the system
// call wants, and `to_cstr` is how a Ferro string becomes one.
pub fn open_read(path: []mut u8) -> !i32 {
var handle: i32 = 0;
unsafe { handle = sys.raw_open(@ptr_cast(u8, &path[0]), 0); }
if handle == 0 - 1 { return error.NoSuchFile; }
return handle;
}
pub fn open_write(path: []mut u8) -> !i32 {
var handle: i32 = 0;
unsafe { handle = sys.raw_open(@ptr_cast(u8, &path[0]), 1); }
if handle == 0 - 1 { return error.CannotWrite; }
return handle;
}
pub fn read(handle: i32, into: []mut u8) -> !usize {
var got: i32 = 0;
unsafe { got = sys.raw_read(handle, @ptr_cast(u8, &into[0]), into.n); }
if got < 0 { return error.ReadFailed; }
return got as usize;
}
pub fn close(handle: i32) -> void {
sys.raw_close(handle);
}
/// Put `text` into `buf` with a NUL after it and say how many bytes that took,
/// the NUL included. A system call cannot be told a length, so it needs this.
///
/// The length comes back rather than a slice of `buf`: with two reference-like
/// parameters the signature cannot say which one a returned slice came from,
/// and R8 will not guess.
pub fn to_cstr(buf: []mut u8, text: []u8) -> usize {
var i: usize = 0;
while i < text.n {
if i + 1 >= buf.n { break; }
buf[i] = text[i];
i = i + 1;
}
if i < buf.n { buf[i] = 0; }
return i + 1;
}
pub fn write_file(handle: i32, bytes: []u8) -> !usize {
var done: i32 = 0;
unsafe { done = sys.raw_write(handle, @ptr_cast(u8, &bytes[0]), bytes.n); }
if done < 0 { return error.WriteFailed; }
return done as usize;
}
/// Copy the command line into `buf` and say how long it is. It arrives as one
/// string with the program's own name first; `arg` picks a piece out of it.
pub fn cmdline(buf: []mut u8) -> usize {
let raw: *u8 = sys.raw_cmdline();
var i: usize = 0;
unsafe {
while i + 1 < buf.n {
let c: u8 = @volatile_load(raw + i);
if c == 0 { break; }
buf[i] = c;
i = i + 1;
}
}
return i;
}
/// The `n`th whitespace-separated piece of `line`, or an empty slice when
/// there is no such piece. Quoting is not handled; nothing here needs it yet.
pub fn arg(line: []u8, n: usize) -> []u8 {
var at: usize = 0;
var seen: usize = 0;
while at < line.n {
while at < line.n {
if line[at] != 32 { break; }
at = at + 1;
}
var stop: usize = at;
while stop < line.n {
if line[stop] == 32 { break; }
stop = stop + 1;
}
if stop > at {
if seen == n { return line[at..stop]; }
seen = seen + 1;
}
at = stop;
}
return line[0..0];
}
+87
View File
@@ -0,0 +1,87 @@
unit std.list;
// A growable sequence. The buffer is owned, so a List owns its elements and
// releasing it releases them (SPEC 5 R1) -- which is also why there is no
// `drop` here: letting go of a List lets go of its buffer on its own. Growth
// doubles, which keeps the total copying proportional to the number of
// pushes.
pub struct List(T) {
items: ^[]mut T,
len: usize,
pub fn with_capacity(n: usize) -> !Self {
let room: ^[]mut T = try mem.alloc_slice(T, n);
return Self{ items: room, len: 0 };
}
pub fn count(self: &Self) -> usize { return self.len; }
pub fn at(self: &Self, i: usize) -> T {
return self.items.^[i];
}
pub fn set(self: &mut Self, i: usize, v: T) -> void {
self.items.^[i] = v;
}
pub fn push(self: &mut Self, v: T) -> !void {
if self.len == self.items.^.n { try self.grow(); }
self.items.^[self.len] = v;
self.len = self.len + 1;
return;
}
/// Take the last one off. Nothing to take is `null`, not a trap.
pub fn pop(self: &mut Self) -> ?T {
if self.len == 0 { return null; }
self.len = self.len - 1;
return self.items.^[self.len];
}
/// Move one out and leave `replacement` where it was (SPEC 5 R7). This is
/// how a `T` that owns something leaves the list without the list ending
/// up with a hole in it.
pub fn take(self: &mut Self, i: usize, replacement: T) -> T {
return mem.replace(&mut self.items.^[i], replacement);
}
/// Exchange two elements.
pub fn swap(self: &mut Self, i: usize, j: usize) -> void {
if i == j { return; }
let first: T = self.items.^[i];
let second: T = mem.replace(&mut self.items.^[j], first);
self.items.^[i] = second;
return;
}
/// The elements as a slice, so `for x in xs.slice()` walks them. R8(a):
/// derived from `self`, so the borrow belongs to the caller.
pub fn slice(self: &Self) -> []T {
return self.items.^[0..self.len];
}
pub fn slice_mut(self: &mut Self) -> []mut T {
return self.items.^[0..self.len];
}
/// Forget the elements and keep the buffer.
pub fn clear(self: &mut Self) -> void { self.len = 0; return; }
/// Move to a buffer twice the size. Kept apart from `push` because the
/// borrow that hands over the old buffer must not be live while the old
/// buffer is still being read (SPEC 5 R6).
fn grow(self: &mut Self) -> !void {
var room: usize = self.items.^.n * 2;
if room == 0 { room = 4; }
let bigger: ^[]mut T = try mem.alloc_slice(T, room);
var i: usize = 0;
while i < self.len {
bigger.^[i] = self.items.^[i];
i = i + 1;
}
let old: ^[]mut T = mem.replace(&mut self.items, bigger);
mem.destroy(old);
return;
}
}
+175
View File
@@ -0,0 +1,175 @@
unit std.map;
// A table from a run of bytes to a value.
//
// A compiler looks names up constantly and a linear scan over a list is the
// wrong shape for that. Keys are copied into one buffer the map owns and each
// slot records where in it the key sits -- the arena-and-handle shape R11 asks
// for, which also means letting go of the map is two frees and not one per
// entry. Both buffers are owned, so R1 does the letting go and no `drop` is
// written here.
//
// Open addressing with linear probing. The table is a power of two so the
// index is a mask rather than a division, and it grows at three quarters full
// because probing gets long well before the table gets full.
pub struct Slot(V) {
at: usize,
len: usize,
used: bool,
value: V,
}
pub struct Map(V) {
slots: ^[]mut Slot(V),
bytes: ^[]mut u8,
used_bytes: usize,
count: usize,
pub fn with_capacity(n: usize) -> !Self {
var room: usize = 8;
while room < n * 2 { room = room * 2; }
let table: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V), room);
let text: ^[]mut u8 = try mem.alloc_slice(u8, 64);
var i: usize = 0;
while i < room {
table.^[i].used = false;
i = i + 1;
}
return Self{ slots: table, bytes: text, used_bytes: 0, count: 0 };
}
pub fn count_of(self: &Self) -> usize { return self.count; }
/// Forget every key but keep the storage. A scope that ends can hand its
/// table to the next one without going back to the allocator, which is
/// what a resolver wants: one table per nesting level, not per function.
pub fn clear(self: &mut Self) -> void {
var i: usize = 0;
while i < self.slots.^.n {
self.slots.^[i].used = false;
i = i + 1;
}
self.used_bytes = 0;
self.count = 0;
return;
}
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
/// Where `key` sits in the table: the slot holding it, or the first free
/// slot it could go in. Probing stops at a free slot, which is why a slot
/// is never cleared -- only ever filled.
fn find(self: &Self, key: []u8) -> usize {
let mask: usize = self.slots.^.n - 1;
var at: usize = hash(key) & mask;
while true {
if not self.slots.^[at].used { return at; }
if self.same(at, key) { return at; }
at = (at + 1) & mask;
}
return 0;
}
fn same(self: &Self, slot: usize, key: []u8) -> bool {
if self.slots.^[slot].len != key.n { return false; }
let from: usize = self.slots.^[slot].at;
var i: usize = 0;
while i < key.n {
if self.bytes.^[from + i] != key[i] { return false; }
i = i + 1;
}
return true;
}
pub fn has(self: &Self, key: []u8) -> bool {
return self.slots.^[self.find(key)].used;
}
pub fn get(self: &Self, key: []u8, missing: V) -> V {
let at: usize = self.find(key);
if self.slots.^[at].used { return self.slots.^[at].value; }
return missing;
}
pub fn put(self: &mut Self, key: []u8, value: V) -> !void {
if self.count * 4 >= self.slots.^.n * 3 { try self.regrow(); }
let at: usize = self.find(key);
if self.slots.^[at].used {
self.slots.^[at].value = value;
return;
}
try self.keep(key, at);
self.slots.^[at].value = value;
self.slots.^[at].used = true;
self.count = self.count + 1;
return;
}
/// Copy `key` into the byte buffer and point the slot at it, moving to a
/// bigger buffer first if it does not fit.
fn keep(self: &mut Self, key: []u8, slot: usize) -> !void {
let at: usize = self.used_bytes;
if at + key.n > self.bytes.^.n {
var room: usize = self.bytes.^.n;
while room < at + key.n { room = room * 2; }
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room);
var k: usize = 0;
while k < at {
bigger.^[k] = self.bytes.^[k];
k = k + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
}
var i: usize = 0;
while i < key.n {
self.bytes.^[at + i] = key[i];
i = i + 1;
}
self.slots.^[slot].at = at;
self.slots.^[slot].len = key.n;
self.used_bytes = at + key.n;
return;
}
/// Twice the slots, everything placed again. The keys do not move: they
/// live in the byte buffer and the slots only point at them.
fn regrow(self: &mut Self) -> !void {
let bigger: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V),
self.slots.^.n * 2);
let mask: usize = bigger.^.n - 1;
var i: usize = 0;
while i < bigger.^.n {
bigger.^[i].used = false;
i = i + 1;
}
i = 0;
while i < self.slots.^.n {
if self.slots.^[i].used {
let from: usize = self.slots.^[i].at;
let len: usize = self.slots.^[i].len;
var at: usize = hash(self.bytes.^[from..from + len]) & mask;
while bigger.^[at].used { at = (at + 1) & mask; }
bigger.^[at] = self.slots.^[i];
}
i = i + 1;
}
let old: ^[]mut Slot(V) = mem.replace(&mut self.slots, bigger);
mem.destroy(old);
return;
}
}
/// FNV-1a. Small, fast, and good enough for identifiers; nothing here has to
/// resist an adversary choosing the keys.
pub fn hash(key: []u8) -> usize {
var h: u32 = 2166136261;
var i: usize = 0;
while i < key.n {
h = h ^ (key[i] as u32);
h = h * 16777619;
i = i + 1;
}
return h as usize;
}
+60
View File
@@ -0,0 +1,60 @@
unit std.mem;
import std.sys;
// `create`, `destroy`, `alloc_slice` and `replace` are compiler intrinsics:
// they need to know the type they are handed, which no signature can say.
// What is written here is what can be written in Ferro.
/// A block of storage handed out in pieces, released all at once.
///
/// SPEC R11 answers recursive and graph-shaped data with an arena that owns
/// the values and integer handles that reference them. This is that arena. A
/// handle is an offset, so it stays valid while the arena does, and comparing
/// two handles is comparing two numbers. The block itself is owned, so nothing
/// here says how to let go of it -- R1 already does.
pub struct Arena {
bytes: ^[]mut u8,
used: usize,
pub fn with_capacity(n: usize) -> !Self {
let room: ^[]mut u8 = try mem.alloc_slice(u8, n);
return Self{ bytes: room, used: 0 };
}
pub fn size(self: &Self) -> usize { return self.used; }
pub fn room(self: &Self) -> usize { return self.bytes.^.n; }
/// Reserve `n` bytes aligned to `align` and give back where they start.
/// Failure is running out of room, which the caller decides what to do
/// about; the arena never grows behind your back, because a handle that
/// moved would no longer mean anything.
pub fn alloc(self: &mut Self, n: usize, align: usize) -> !usize {
var at: usize = self.used;
if align > 1 {
let over: usize = at % align;
if over != 0 { at = at + align - over; }
}
if at + n > self.bytes.^.n { return error.ArenaFull; }
self.used = at + n;
return at;
}
/// One byte at a handle. Reading and writing go through here so that a
/// handle can be checked once, in one place.
pub fn at(self: &Self, handle: usize) -> !u8 {
if handle >= self.used { return error.BadHandle; }
return self.bytes.^[handle];
}
pub fn put(self: &mut Self, handle: usize, value: u8) -> !void {
if handle >= self.used { return error.BadHandle; }
self.bytes.^[handle] = value;
return;
}
/// Forget everything handed out so far. Every handle from before is stale;
/// that is the trade an arena makes.
pub fn reset(self: &mut Self) -> void { self.used = 0; }
}
+66
View File
@@ -0,0 +1,66 @@
unit std.str;
// `str` is `[]u8` (SPEC 4.2), so these take and give plain byte slices.
pub fn eq(a: []u8, b: []u8) -> bool {
if a.n != b.n { return false; }
var i: usize = 0;
while i < a.n {
if a[i] != b[i] { return false; }
i = i + 1;
}
return true;
}
pub fn starts_with(s: []u8, prefix: []u8) -> bool {
if prefix.n > s.n { return false; }
return eq(s[0..prefix.n], prefix);
}
/// Where `needle` first appears in `s`, or the length of `s` when it does not.
/// An index past the end is how "not found" is said without an optional.
pub fn find(s: []u8, needle: []u8) -> usize {
if needle.n == 0 { return 0; }
if needle.n > s.n { return s.n; }
var at: usize = 0;
let last: usize = s.n - needle.n;
while at <= last {
if eq(s[at..at + needle.n], needle) { return at; }
at = at + 1;
}
return s.n;
}
pub fn trim(s: []u8) -> []u8 {
var from: usize = 0;
var to: usize = s.n;
while from < to {
if s[from] != 32 and s[from] != 9 and s[from] != 10 and s[from] != 13 {
break;
}
from = from + 1;
}
while to > from {
let c: u8 = s[to - 1];
if c != 32 and c != 9 and c != 10 and c != 13 { break; }
to = to - 1;
}
return s[from..to];
}
pub fn parse_int(s: []u8) -> ?i32 {
if s.n == 0 { return null; }
var value: i32 = 0;
var i: usize = 0;
var negative: bool = false;
if s[0] == 45 { negative = true; i = 1; }
if i >= s.n { return null; }
while i < s.n {
let c: u8 = s[i];
if c < 48 or c > 57 { return null; }
value = value * 10 + ((c - 48) as i32);
i = i + 1;
}
if negative { return 0 - value; }
return value;
}
+53
View File
@@ -0,0 +1,53 @@
unit std.sys;
// The few things the language cannot say for itself. The runtime provides
// them; everything else in the standard library is written in Ferro.
extern "c" fn fe_rt_write(handle: i32, bytes: *u8, len: usize) -> i32;
extern "c" fn fe_rt_alloc(n: usize) -> *u8;
extern "c" fn fe_rt_free(p: *u8);
extern "c" fn fe_rt_exit(code: i32);
extern "c" fn fe_rt_allocs() -> i32;
extern "c" fn fe_rt_frees() -> i32;
extern "c" fn fe_rt_open(path: *u8, write: i32) -> i32;
extern "c" fn fe_rt_read(handle: i32, buf: *u8, len: usize) -> i32;
extern "c" fn fe_rt_close(handle: i32);
extern "c" fn fe_rt_cmdline() -> *u8;
pub fn exit(code: i32) -> void {
unsafe { fe_rt_exit(code); }
}
pub fn raw_write(handle: i32, bytes: *u8, len: usize) -> i32 {
unsafe { return fe_rt_write(handle, bytes, len); }
}
pub fn raw_alloc(n: usize) -> *u8 {
unsafe { return fe_rt_alloc(n); }
}
pub fn raw_free(p: *u8) -> void {
unsafe { fe_rt_free(p); }
}
// How many times the allocator was asked to hand out memory, and to take it
// back. A test can insist the two agree; nothing else should care.
pub fn allocs() -> i32 { unsafe { return fe_rt_allocs(); } }
pub fn frees() -> i32 { unsafe { return fe_rt_frees(); } }
pub fn raw_open(path: *u8, write: i32) -> i32 {
unsafe { return fe_rt_open(path, write); }
}
pub fn raw_read(handle: i32, buf: *u8, len: usize) -> i32 {
unsafe { return fe_rt_read(handle, buf, len); }
}
pub fn raw_close(handle: i32) -> void {
unsafe { fe_rt_close(handle); }
}
/// The whole command line as one NUL-terminated string. Splitting it into
/// arguments is `std.io`'s job: the runtime should not know about quoting.
pub fn raw_cmdline() -> *u8 {
unsafe { return fe_rt_cmdline(); }
}
+39
View File
@@ -0,0 +1,39 @@
// EXIT:0
// OUTPUT:handles 0 4 8
// OUTPUT:value 65
// OUTPUT:full
// OUTPUT:reset 0
// OUTPUT:balanced
unit arena;
import std.io;
import std.mem;
import std.sys;
// SPEC R11: recursive and graph-shaped data is answered by an arena that owns
// the values and integer handles that point into it. This is that shape.
fn run() -> !void {
var a: mem.Arena = try mem.Arena.with_capacity(16);
let first: usize = try a.alloc(4, 4);
let second: usize = try a.alloc(4, 4);
let third: usize = try a.alloc(4, 4);
@print("handles {} {} {}\n", first, second, third);
try a.put(first, 65);
let got: u8 = try a.at(first);
@print("value {}\n", got);
let over: usize = a.alloc(64, 1) catch |e| {
@print("full\n");
a.reset();
@print("reset {}\n", a.size());
return;
};
@print("unexpected room {}\n", over);
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() != sys.frees() { @print("leaked\n"); return 2; }
@print("balanced\n");
return 0;
}
+81
View File
@@ -0,0 +1,81 @@
// EXIT:0
// OUTPUT:alloc 10 20 30 len 3
// OUTPUT:freed len 2 stale -1 live 30
// OUTPUT:reused index 1 old -1 new 99
// OUTPUT:swap 99 10
// OUTPUT:set 77 take 77 after 5
// OUTPUT:reset len 0 before -1
// OUTPUT:other -1
// OUTPUT:full yes
// OUTPUT:balanced
unit arenat;
import std.io;
import std.sys;
import std.arena;
// SPEC R11: the arena owns the values and a handle is a number. The point of
// the number carrying a generation is that using it after the slot was given
// back answers `null` rather than whatever moved in afterwards.
fn run() -> !void {
var a: arena.Arena(i32) = try arena.Arena(i32).with_capacity(1, 3);
let x: arena.Handle(i32) = try a.alloc(10);
let y: arena.Handle(i32) = try a.alloc(20);
let z: arena.Handle(i32) = try a.alloc(30);
@print("alloc {} {} {} len {}\n", a.get(x) orelse -1, a.get(y) orelse -1,
a.get(z) orelse -1, a.len());
// Give one back. Its handle stops meaning anything; the others do not.
let gone: bool = a.free(y);
@print("freed len {} stale {} live {}\n", a.len(), a.get(y) orelse -1,
a.get(z) orelse -1);
// The slot comes back on the free list, and the old handle still does not
// match the new occupant.
let again: arena.Handle(i32) = try a.alloc(99);
@print("reused index {} old {} new {}\n", again.index,
a.get(y) orelse -1, a.get(again) orelse -1);
let ok: bool = a.swap(x, again);
@print("swap {} {}\n", a.get(x) orelse -1, a.get(again) orelse -1);
let wrote: bool = a.set(x, 77);
let took: i32 = a.take(x, 5) orelse -1;
@print("set {} take {} after {}\n", 77, took, a.get(x) orelse -1);
// Reset moves the epoch, so every handle made before it is stale without
// a single slot being touched.
a.reset();
@print("reset len {} before {}\n", a.len(), a.get(x) orelse -1);
// A handle from one arena means nothing to another.
var b: arena.Arena(i32) = try arena.Arena(i32).with_capacity(2, 2);
let h: arena.Handle(i32) = try b.alloc(41);
@print("other {}\n", a.get(h) orelse -1);
// Running out of room is an error, not a trap.
var full: bool = false;
fill(&mut b) catch |e| { full = true; };
@print("full {}\n", yesno(full));
return;
}
/// Two more into an arena that has room for one.
fn fill(b: &mut arena.Arena(i32)) -> !void {
let p: arena.Handle(i32) = try b.alloc(1);
let q: arena.Handle(i32) = try b.alloc(2);
return;
}
fn yesno(b: bool) -> []u8 {
if b { return "yes"; }
return "no";
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}
+12
View File
@@ -0,0 +1,12 @@
// EXIT:10
unit arith;
fn main() -> i32 {
let a: i32 = 7;
let b: i32 = 6;
var r: i32 = a * b;
r = r - 2;
r = r / 4;
if r == 10 { return r; }
return 99;
}
+13
View File
@@ -0,0 +1,13 @@
// EXIT:100
unit array;
fn main() -> i32 {
let a: [4]i32 = [10, 20, 30, 40];
var sum: i32 = 0;
var i: i32 = 0;
while i < 4 {
sum = sum + a[i];
i = i + 1;
}
return sum;
}
+37
View File
@@ -0,0 +1,37 @@
// EXIT:0
// OUTPUT:i32 -1 -6 4
// OUTPUT:u8 255 250
// OUTPUT:mask 240 15
// OUTPUT:prec 7 3
unit bitnot;
import std.io;
// `~` flips every bit (SPEC §6.2, 단항). `^` is taken by xor and by `.^`, so
// bitwise NOT needs its own spelling.
//
// `|`, `^` and `&` are three different levels, in C's order. Merging `|` and
// `^` would make `a | b ^ c` bind as `(a | b) ^ c`, which is not what anyone
// coming from C reads it as.
fn main() -> i32 {
let a: i32 = 0;
let b: i32 = 5;
let c: i32 = -5;
@print("i32 {} {} {}\n", ~a, ~b, ~c);
let u: u8 = 0;
let v: u8 = 5;
@print("u8 {} {}\n", (~u) as i32, (~v) as i32);
// Clearing bits is what the operator is for.
let bits: u8 = 255;
let low: u8 = 15;
@print("mask {} {}\n", (bits & ~low) as i32, (bits & low) as i32);
// 1 | 2 ^ 4 is 1 | (2 ^ 4) = 1 | 6 = 7, not (1 | 2) ^ 4 = 3 ^ 4 = 7.
// Those agree, so pick operands that do not: 3 | 1 ^ 2 is 3 | 3 = 3,
// while (3 | 1) ^ 2 would be 3 ^ 2 = 1. The 3 is the proof.
@print("prec {} {}\n", 1 | 2 ^ 4, 3 | 1 ^ 2);
return 0;
}
+10
View File
@@ -0,0 +1,10 @@
// EXIT:3
// OUTPUT:index out of bounds
// NOCHECKS:0
unit bounds;
fn main() -> i32 {
let a: [2]i32 = [1, 2];
let x: i32 = a[2];
return x - x;
}
+120
View File
@@ -0,0 +1,120 @@
// EXIT:0
// OUTPUT:1+2*3 = 7
// OUTPUT:(1+2)*3 = 9
// OUTPUT:2*(3+4)-5 = 9
// OUTPUT:10/3 = 3
// OUTPUT:1+ = error
// OUTPUT:(1+2 = error
unit calc;
import std.io;
import std.fmt;
// A recursive-descent evaluator over a byte slice.
//
// The position travels in a `&mut usize` rather than in a struct beside the
// text: a struct cannot hold a slice, because a slice is a borrowed view and
// R4 keeps borrows out of aggregate storage. Passing both is the honest way
// to say "this text, and how far we have read".
fn done(src: []u8, at: usize) -> bool { return at >= src.n; }
fn peek(src: []u8, at: usize) -> u8 {
if done(src, at) { return 0; }
return src[at];
}
fn skip_spaces(src: []u8, at: &mut usize) -> void {
while not done(src, at.^) {
if src[at.^] != 32 { break; }
at.^ = at.^ + 1;
}
}
fn number(src: []u8, at: &mut usize) -> !i32 {
var value: i32 = 0;
var digits: usize = 0;
while not done(src, at.^) {
let c: u8 = src[at.^];
if c < 48 or c > 57 { break; }
value = value * 10 + ((c - 48) as i32);
digits = digits + 1;
at.^ = at.^ + 1;
}
if digits == 0 { return error.BadNumber; }
return value;
}
fn factor(src: []u8, at: &mut usize) -> !i32 {
skip_spaces(src, at);
if peek(src, at.^) == 40 {
at.^ = at.^ + 1;
let inner: i32 = try expr(src, at);
skip_spaces(src, at);
if peek(src, at.^) != 41 { return error.Unbalanced; }
at.^ = at.^ + 1;
return inner;
}
return number(src, at);
}
fn term(src: []u8, at: &mut usize) -> !i32 {
var left: i32 = try factor(src, at);
while true {
skip_spaces(src, at);
let op: u8 = peek(src, at.^);
if op != 42 and op != 47 { break; }
at.^ = at.^ + 1;
let right: i32 = try factor(src, at);
if op == 42 { left = left * right; }
else {
if right == 0 { return error.DivideByZero; }
left = left / right;
}
}
return left;
}
fn expr(src: []u8, at: &mut usize) -> !i32 {
var left: i32 = try term(src, at);
while true {
skip_spaces(src, at);
let op: u8 = peek(src, at.^);
if op != 43 and op != 45 { break; }
at.^ = at.^ + 1;
let right: i32 = try term(src, at);
if op == 43 { left = left + right; }
else { left = left - right; }
}
return left;
}
fn evaluate(text: []u8) -> !i32 {
var at: usize = 0;
let value: i32 = try expr(text, &mut at);
skip_spaces(text, &mut at);
if not done(text, at) { return error.Trailing; }
return value;
}
fn show(text: []u8) -> void {
var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
io.print(text);
io.print(" = ");
let value: i32 = evaluate(text) catch |e| {
io.print("error\n");
return;
};
let n: usize = fmt.fmt_i32(buf[..], value);
io.print(buf[0..n]);
io.print("\n");
}
fn main() -> i32 {
show("1+2*3");
show("(1+2)*3");
show("2*(3+4)-5");
show("10/3");
show("1+");
show("(1+2");
return 0;
}
+21
View File
@@ -0,0 +1,21 @@
// EXIT:0
// OUTPUT:args ok
unit cmdargs;
import std.io;
import std.str;
// The command line reaches the program. A compiler is told which file to read
// this way and no other.
fn main() -> i32 {
var line: [512]u8 = undefined;
let n: usize = io.cmdline(line[..]);
let program: []u8 = io.arg(line[0..n], 0);
if program.n == 0 { @print("no program name\n"); return 1; }
if str.find(program, "cmdargs") == program.n {
@print("unexpected program name: {}\n", program);
return 2;
}
@print("args ok\n");
return 0;
}
+22
View File
@@ -0,0 +1,22 @@
// EXIT:21921
// body(true): defer 2 then defer 1 -> 2, 21
// body(false): note(9) first -> 219, 2192, 21921
unit defers;
var log: i32 = 0;
fn note(v: i32) -> void { log = log * 10 + v; }
fn body(early: bool) -> i32 {
defer { note(1); }
defer { note(2); }
if early { return 0; }
note(9);
return 0;
}
fn main() -> i32 {
body(true);
body(false);
return log;
}
+18
View File
@@ -0,0 +1,18 @@
// EXIT:9
unit errunion;
fn half(v: i32) -> !i32 {
if v == 0 { return error.Empty; }
return v / 2;
}
fn chain(v: i32) -> !i32 {
let h: i32 = try half(v);
return h + 1;
}
fn main() -> i32 {
let good: i32 = chain(16) catch 100;
let bad: i32 = chain(0) catch 0;
return good + bad;
}
+81
View File
@@ -0,0 +1,81 @@
// EXIT:0
// OUTPUT:a 40 b 3
// OUTPUT:used 5 room 8
// OUTPUT:first 9
// OUTPUT:balanced
unit fieldbrw;
import std.io;
import std.sys;
import std.mem;
// Borrowing is per field. `p.a` and `p.b` are different places, so lending one
// out has to leave the other readable -- otherwise a method cannot write
// through one field while reading another, which is most of what a method
// does.
struct Pair { a: i32, b: i32, }
fn scale(v: &mut i32, by: i32) -> void {
v.^ = v.^ * by;
return;
}
struct Box {
bytes: ^[]mut u8,
used: usize,
room: usize,
fn with_capacity(n: usize) -> !Self {
let room: ^[]mut u8 = try mem.alloc_slice(u8, n);
return Self{ bytes: room, used: 0, room: n };
}
/// Move to a bigger buffer, then go on reading the other fields. The
/// borrow that hands over the buffer covers `bytes` and nothing else.
fn grow(self: &mut Self, want: usize) -> !void {
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, want);
var i: usize = 0;
while i < self.used {
bigger.^[i] = self.bytes.^[i];
i = i + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
self.room = want;
return;
}
fn push(self: &mut Self, v: u8) -> !void {
if self.used == self.room { try self.grow(self.room * 2); }
self.bytes.^[self.used] = v;
self.used = self.used + 1;
return;
}
}
fn run() -> !void {
var p: Pair = Pair{ a: 4, b: 2 };
let left: &mut i32 = &mut p.a;
// Writing another field while `a` is lent out.
p.b = 3;
scale(left, 10);
@print("a {} b {}\n", p.a, p.b);
var box: Box = try Box.with_capacity(4);
try box.push(9);
try box.push(8);
try box.push(7);
try box.push(6);
try box.push(5);
@print("used {} room {}\n", box.used, box.room);
@print("first {}\n", box.bytes.^[0]);
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}
+49
View File
@@ -0,0 +1,49 @@
// EXIT:0
// OUTPUT:n 777 m 999
// OUTPUT:len 12 first 7
// OUTPUT:count 3
// OUTPUT:balanced
unit fieldn;
import std.io;
import std.sys;
import std.mem;
// `.n` on a slice is its length. On a struct it is whatever field is called
// `n` -- and a struct is allowed to call a field that. Reading one as the
// other hands back the four bytes beside the pointer, which is a plausible
// number and so goes unnoticed.
struct Box {
room: ^[]mut u8,
n: usize,
m: usize,
}
struct Counter {
n: usize,
fn bump(self: &mut Self) -> void { self.n = self.n + 1; return; }
}
fn run() -> !void {
let r: ^[]mut u8 = try mem.alloc_slice(u8, 12);
r.^[0] = 7;
let b: Box = Box{ room: r, n: 777, m: 999 };
@print("n {} m {}\n", b.n, b.m);
// The slice beside it still answers with its length.
@print("len {} first {}\n", b.room.^.n, b.room.^[0]);
var c: Counter = Counter{ n: 0 };
c.bump();
c.bump();
c.bump();
@print("count {}\n", c.n);
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}
+11
View File
@@ -0,0 +1,11 @@
// EXIT:60
unit forloop;
fn main() -> i32 {
let a: [5]i32 = [4, 8, 12, 16, 20];
var sum: i32 = 0;
for v in a {
sum = sum + v.^;
}
return sum;
}
+21
View File
@@ -0,0 +1,21 @@
// EXIT:39
unit generic;
struct Box(T) {
value: T,
pub fn new(v: T) -> Self { return Self{ value: v }; }
pub fn get(self: &Self) -> T { return self.value; }
}
fn id(comptime T: type, v: T) -> T { return v; }
fn twice(comptime T: type, v: T) -> T { return v + v; }
fn main() -> i32 {
let a: i32 = id(i32, 7);
let b: u8 = id(u8, 9 as u8);
let c: i32 = twice(i32, 10);
let box: Box(i32) = Box(i32).new(3);
return a + (b as i32) + c + box.get();
}
+8
View File
@@ -0,0 +1,8 @@
# 제네릭 인스턴스 리터럴
`Name(args){...}``binding.Name(args){...}` 로 제네릭의 인스턴스를 짓는다.
전에는 `Self{...}` 나 생성자 함수로만 만들 수 있었다.
`Handle(T)``T` 를 본문에서 쓰지 않는다 -- typed handle 의 자연스러운 모양이고,
`Handle(Node)``Handle(Kind)` 를 갈라놓는 것 말고는 하는 일이 없다. 그 둘이
실제로 다른 타입이라는 것은 `generic/badphant.fe` 가 거부로 고정한다.
+14
View File
@@ -0,0 +1,14 @@
unit hold;
// Fields are `pub` so another unit can write the literal directly. A generic
// with a `pub` field is the only way to reach `binding.Name(args){...}`.
pub struct Cell(T) {
pub v: T,
}
// `T` is never used in the body. That is the natural shape of a typed handle:
// the parameter is there to keep `Handle(Node)` and `Handle(Type)` apart, not
// to describe any storage.
pub struct Handle(T) {
pub raw: u32,
}
+38
View File
@@ -0,0 +1,38 @@
// EXIT:0
// OUTPUT:cell 7 41
// OUTPUT:handle 3 4 sum 7
// OUTPUT:local 9
unit main;
import std.io;
import hold;
struct Node { v: i32, }
struct Kind { v: i32, }
// A generic declared here, instantiated with an explicit argument below.
struct Boxed(T) {
v: T,
}
// Two instances of the same phantom generic are different nominal types, so
// this only accepts one of them.
fn only_node(h: hold.Handle(Node)) -> u32 { return h.raw; }
fn only_kind(h: hold.Handle(Kind)) -> u32 { return h.raw; }
fn main() -> i32 {
// `binding.Name(args){...}` -- a generic instance from another unit.
let a: hold.Cell(i32) = hold.Cell(i32){ v: 7 };
let b: hold.Cell(u8) = hold.Cell(u8){ v: 41 };
@print("cell {} {}\n", a.v, b.v);
let n: hold.Handle(Node) = hold.Handle(Node){ raw: 3 };
let k: hold.Handle(Kind) = hold.Handle(Kind){ raw: 4 };
@print("handle {} {} sum {}\n", only_node(n), only_kind(k),
only_node(n) + only_kind(k));
// `Name(args){...}` -- a generic declared in this unit.
let c: Boxed(i32) = Boxed(i32){ v: 9 };
@print("local {}\n", c.v);
return 0;
}
+30
View File
@@ -0,0 +1,30 @@
// EXIT:0
// OUTPUT:sum 4950
unit heap;
import std.io;
import std.fmt;
fn build(n: usize) -> !^[]mut i32 {
var cells: ^[]mut i32 = try mem.alloc_slice(i32, n);
var i: usize = 0;
while i < n {
cells.^[i] = i as i32;
i = i + 1;
}
return cells;
}
fn main() -> i32 {
let cells: ^[]mut i32 = build(100) catch |e| { return 1; };
defer { mem.destroy(cells); }
var sum: i32 = 0;
for v in cells.^ {
sum = sum + v.^;
}
var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
let k: usize = fmt.fmt_i32(buf[..], sum);
io.print("sum ");
io.print(buf[0..k]);
io.print("\n");
return 0;
}
+9
View File
@@ -0,0 +1,9 @@
// EXIT:0
// OUTPUT:hello from ferro
unit hello;
import std.io;
fn main() -> i32 {
io.println("hello from ferro");
return 0;
}
+73
View File
@@ -0,0 +1,73 @@
// EXIT:0
// OUTPUT:ids 0 1 2 count 3
// OUTPUT:again 0 same yes count 3
// OUTPUT:eq yes no len 4
// OUTPUT:find 1 missing yes
// OUTPUT:hash steady yes apart yes
// OUTPUT:copied unit 4
// OUTPUT:scope x 10 y 20 of 2
// OUTPUT:balanced
unit interns;
import std.io;
import std.sys;
import std.intern;
import std.map;
// One copy of every distinct name, and a number that stands for it. Comparing
// two names is comparing two integers; storing one costs four bytes and no
// ownership.
//
// There is deliberately no way to get a `str` back out: a borrow of the text
// would be a borrow of the interner, and the interner is exactly what a parser
// wants to keep adding to while it holds names.
fn run() -> !void {
var t: intern.Interner = try intern.Interner.with_capacity(8);
let a: intern.StrId = try t.intern("unit");
let b: intern.StrId = try t.intern("fn");
let c: intern.StrId = try t.intern("struct");
@print("ids {} {} {} count {}\n", a.raw, b.raw, c.raw, t.count_of());
// The same name twice is the same number, and costs nothing new.
let again: intern.StrId = try t.intern("unit");
@print("again {} same {} count {}\n", again.raw, yesno(a.same(again)),
t.count_of());
@print("eq {} {} len {}\n", yesno(t.eq(a, "unit")), yesno(t.eq(a, "fn")),
t.len_of(a));
@print("find {} missing {}\n", t.find("fn"),
yesno(t.find("nope") == intern.NONE));
// A name hashes the same every time, and two names do not collide here.
@print("hash steady {} apart {}\n", yesno(t.hash_of(a) == t.hash_of(again)),
yesno(t.hash_of(a) != t.hash_of(b)));
// The only way to see the text: copy it somewhere you own.
var buf: [8]u8 = undefined;
let n: usize = t.copy_into(a, buf[..]);
@print("copied {} {}\n", buf[0..n], n);
// A symbol table is a Map keyed on the name's number. std.map keys on
// bytes, so no separate integer-keyed map is needed.
var scope: map.Map(i32) = try map.Map(i32).with_capacity(8);
var key: [4]u8 = undefined;
try scope.put(intern.key_of(a, key[..]), 10);
try scope.put(intern.key_of(b, key[..]), 20);
@print("scope x {} y {} of {}\n", scope.get(intern.key_of(a, key[..]), -1),
scope.get(intern.key_of(b, key[..]), -1), scope.count_of());
return;
}
fn yesno(b: bool) -> []u8 {
if b { return "yes"; }
return "no";
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
# Ferro 로 쓴 Ferro 프런트엔드
셀프호스팅의 강제 함수. 언어가 자기 컴파일러를 쓸 만한지는 써 봐야 안다.
| 파일 | 무엇 |
|---|---|
| `tok.fe` | 토큰. 텍스트를 들지 않고 소스 안의 위치를 든다 (R4) |
| `scan.fe` | 렉서. `next(src, &mut at, &mut line)` |
| `main.fe` | 렉서 프로그램. 종류별로 세고 몇 개를 찍는다 |
| `ast.fe` | 노드. 자식은 노드 배열 안의 인덱스다 |
| `parse.fe` | 재귀 하강 파서. 토큰 하나를 앞서 본다 |
| `tree.fe` | 파서 프로그램. 식을 전위 표기로 다시 찍는다 |
전위 표기로 찍는 것이 요점이다. `1 + 2 * 3``(+ 1 (* 2 3))` 로 나오는 것
말고는 우선순위가 맞았는지 볼 방법이 없다.
+53
View File
@@ -0,0 +1,53 @@
unit ast;
// The tree the parser builds.
//
// Nodes live in one growing array and refer to each other by index, which is
// what SPEC R11 asks for: an owner that holds the values and handles that
// point at them. A node cannot hold a `^Node` for its children because a node
// has several and they are not each owned once; it cannot hold a `&Node`
// because R4 keeps borrows out of aggregate storage. An index is neither.
pub enum Shape {
Unit, // a: name
Fn, // a: name, b: first statement
Let, // a: name, b: value
Return, // a: value, or NONE
Binary, // a: left, b: right, from/len: the operator
Number,
Name,
Text,
Error,
}
/// No node. Zero is a real index, so the empty handle is the largest one.
pub const NONE: usize = 4294967295;
pub struct Node {
pub shape: Shape,
pub from: usize, // where in the source this came from
pub len: usize,
pub line: usize,
pub a: usize, // handles into the same tree
pub b: usize,
pub next: usize, // the following statement, when there is one
/// What a `Name` resolved to: the handle of the `Let` or `Fn` that
/// declared it. The resolver writes this back so the tree carries its own
/// answers and nothing has to look the name up a second time.
pub bind: usize,
}
pub fn name_of(s: Shape) -> []u8 {
match s {
Unit => { return "unit"; }
Fn => { return "fn"; }
Let => { return "let"; }
Return => { return "return"; }
Binary => { return "binary"; }
Number => { return "number"; }
Name => { return "name"; }
Text => { return "text"; }
Error => { return "error"; }
}
return "?";
}
+61
View File
@@ -0,0 +1,61 @@
// EXIT:0
// OUTPUT:first keyword unit @1
// OUTPUT:number 42 @3
// OUTPUT:text "hi" @3
// OUTPUT:arrow -> @5
// OUTPUT:keyword 6 name 7 number 1 text 1 punct 15
// OUTPUT:total 30
unit main;
import std.io;
import tok;
import scan;
// The Ferro lexer, written in Ferro. This is the shape a self-hosted `fec`
// would take: read a source, hand back tokens, say where each came from.
const SOURCE: str = "unit demo;\n\nfn answer() { let n = 42; let s = \"hi\"; }\n// a comment\nfn arrow() -> i32 { return n; }\n";
fn main() -> i32 {
var at: usize = 0;
var line: usize = 1;
var keywords: usize = 0;
var names: usize = 0;
var numbers: usize = 0;
var texts: usize = 0;
var puncts: usize = 0;
var total: usize = 0;
var first: bool = true;
while true {
let t: tok.Token = scan.next(SOURCE, &mut at, &mut line);
if t.kind == tok.Kind.End { break; }
total = total + 1;
if first {
@print("first {} {} @{}\n", tok.name_of(t.kind),
tok.text(SOURCE, t), t.line);
first = false;
}
match t.kind {
Keyword => { keywords = keywords + 1; }
Name => { names = names + 1; }
Number => {
numbers = numbers + 1;
@print("number {} @{}\n", tok.text(SOURCE, t), t.line);
}
Text => {
texts = texts + 1;
@print("text {} @{}\n", tok.text(SOURCE, t), t.line);
}
Punct => {
puncts = puncts + 1;
if t.len == 2 {
@print("arrow {} @{}\n", tok.text(SOURCE, t), t.line);
}
}
_ => { @print("unexpected {}\n", tok.name_of(t.kind)); }
}
}
@print("keyword {} name {} number {} text {} punct {}\n",
keywords, names, numbers, texts, puncts);
@print("total {}\n", total);
return 0;
}
+256
View File
@@ -0,0 +1,256 @@
unit parse;
import std.list;
import std.str;
import tok;
import scan;
import ast;
// Recursive descent over the lexer's tokens.
//
// The source is not a field: R4 keeps borrows out of aggregate storage, so
// `src` is passed to every step, the same way the lexer passes it. What the
// parser does own is the node array, and every parent points at its children
// by index into it.
//
// One token of lookahead lives in `cur`. That is all this grammar needs.
pub struct Parser {
nodes: list.List(ast.Node),
at: usize,
line: usize,
cur: tok.Token,
pub errors: usize,
pub fn on(src: []u8) -> !Self {
var p: Self = Self{
nodes: try list.List(ast.Node).with_capacity(16),
at: 0,
line: 1,
cur: tok.Token{ kind: tok.Kind.End, from: 0, len: 0, line: 1 },
errors: 0,
};
p.bump(src);
return p;
}
fn bump(self: &mut Self, src: []u8) -> void {
self.cur = scan.next(src, &mut self.at, &mut self.line);
return;
}
fn done(self: &Self) -> bool { return self.cur.kind == tok.Kind.End; }
/// Is the token in hand this exact spelling?
fn is(self: &Self, src: []u8, want: []u8) -> bool {
return str.eq(tok.text(src, self.cur), want);
}
/// Take the token in hand if it is this spelling; say whether it was.
fn eat(self: &mut Self, src: []u8, want: []u8) -> bool {
if not self.is(src, want) { return false; }
self.bump(src);
return true;
}
/// Demand this spelling. A miss is counted and the token stays put, so the
/// caller decides how to get back on its feet.
fn want(self: &mut Self, src: []u8, w: []u8) -> bool {
if self.eat(src, w) { return true; }
self.errors = self.errors + 1;
return false;
}
fn add(self: &mut Self, s: ast.Shape, t: tok.Token, a: usize,
b: usize) -> !usize {
let n: ast.Node = ast.Node{
shape: s, from: t.from, len: t.len, line: t.line,
a: a, b: b, next: ast.NONE,
// The parser does not resolve names; the resolver writes this.
bind: ast.NONE,
};
let i: usize = self.nodes.count();
try self.nodes.push(n);
return i;
}
// -- reading the tree back ------------------------------------------
pub fn count(self: &Self) -> usize { return self.nodes.count(); }
pub fn node(self: &Self, i: usize) -> ast.Node {
return self.nodes.at(i);
}
// -- the grammar ----------------------------------------------------
//
// unit := "unit" NAME ";" item*
// item := "fn" NAME "(" ")" block
// block := "{" stmt* "}"
// stmt := "let" NAME "=" expr ";" | "return" expr? ";"
// expr := term (("+" | "-") term)*
// term := factor (("*" | "/") factor)*
// factor := NUMBER | NAME | TEXT | "(" expr ")"
pub fn unit_decl(self: &mut Self, src: []u8) -> !usize {
let ok: bool = self.want(src, "unit");
let name: tok.Token = self.cur;
if ok { self.bump(src); }
let semi: bool = self.want(src, ";");
let root: usize = try self.add(ast.Shape.Unit, name, ast.NONE,
ast.NONE);
var first: usize = ast.NONE;
var last: usize = ast.NONE;
while not self.done() {
let it: usize = try self.item(src);
if first == ast.NONE { first = it; }
else { self.link(last, it); }
last = it;
}
self.set_a(root, first);
return root;
}
/// Point one statement or item at the one after it.
fn link(self: &mut Self, from: usize, to: usize) -> void {
var n: ast.Node = self.nodes.at(from);
n.next = to;
self.nodes.set(from, n);
return;
}
fn set_a(self: &mut Self, at: usize, a: usize) -> void {
var n: ast.Node = self.nodes.at(at);
n.a = a;
self.nodes.set(at, n);
return;
}
fn item(self: &mut Self, src: []u8) -> !usize {
if not self.is(src, "fn") {
let bad: tok.Token = self.cur;
self.errors = self.errors + 1;
self.skip_stmt(src);
return try self.add(ast.Shape.Error, bad, ast.NONE, ast.NONE);
}
self.bump(src);
let name: tok.Token = self.cur;
self.bump(src);
let open: bool = self.want(src, "(");
let close: bool = self.want(src, ")");
let body: usize = try self.block(src);
return try self.add(ast.Shape.Fn, name, ast.NONE, body);
}
fn block(self: &mut Self, src: []u8) -> !usize {
let open: bool = self.want(src, "{");
var first: usize = ast.NONE;
var last: usize = ast.NONE;
while true {
if self.done() { break; }
if self.is(src, "}") { break; }
let s: usize = try self.stmt(src);
if first == ast.NONE { first = s; }
else { self.link(last, s); }
last = s;
}
let close: bool = self.want(src, "}");
return first;
}
fn stmt(self: &mut Self, src: []u8) -> !usize {
if self.is(src, "let") {
self.bump(src);
let name: tok.Token = self.cur;
self.bump(src);
let has_eq: bool = self.want(src, "=");
let value: usize = try self.expr(src);
let semi: bool = self.want(src, ";");
return try self.add(ast.Shape.Let, name, ast.NONE, value);
}
if self.is(src, "return") {
let head: tok.Token = self.cur;
self.bump(src);
if self.is(src, ";") {
self.bump(src);
return try self.add(ast.Shape.Return, head, ast.NONE,
ast.NONE);
}
let value: usize = try self.expr(src);
let semi: bool = self.want(src, ";");
return try self.add(ast.Shape.Return, head, value, ast.NONE);
}
let bad: tok.Token = self.cur;
self.errors = self.errors + 1;
self.skip_stmt(src);
return try self.add(ast.Shape.Error, bad, ast.NONE, ast.NONE);
}
/// Get back to a statement boundary after something unrecognised. Stopping
/// at `;` or `}` means one bad statement costs one diagnostic, not a run
/// of them.
fn skip_stmt(self: &mut Self, src: []u8) -> void {
while not self.done() {
if self.is(src, "}") { return; }
if self.is(src, ";") { self.bump(src); return; }
self.bump(src);
}
return;
}
fn expr(self: &mut Self, src: []u8) -> !usize {
var left: usize = try self.term(src);
while true {
if self.done() { break; }
let plus: bool = self.is(src, "+");
let minus: bool = self.is(src, "-");
if not plus and not minus { break; }
let op: tok.Token = self.cur;
self.bump(src);
let right: usize = try self.term(src);
left = try self.add(ast.Shape.Binary, op, left, right);
}
return left;
}
fn term(self: &mut Self, src: []u8) -> !usize {
var left: usize = try self.factor(src);
while true {
if self.done() { break; }
let star: bool = self.is(src, "*");
let slash: bool = self.is(src, "/");
if not star and not slash { break; }
let op: tok.Token = self.cur;
self.bump(src);
let right: usize = try self.factor(src);
left = try self.add(ast.Shape.Binary, op, left, right);
}
return left;
}
fn factor(self: &mut Self, src: []u8) -> !usize {
let t: tok.Token = self.cur;
if t.kind == tok.Kind.Number {
self.bump(src);
return try self.add(ast.Shape.Number, t, ast.NONE, ast.NONE);
}
if t.kind == tok.Kind.Name {
self.bump(src);
return try self.add(ast.Shape.Name, t, ast.NONE, ast.NONE);
}
if t.kind == tok.Kind.Text {
self.bump(src);
return try self.add(ast.Shape.Text, t, ast.NONE, ast.NONE);
}
if self.is(src, "(") {
self.bump(src);
let inner: usize = try self.expr(src);
let close: bool = self.want(src, ")");
return inner;
}
self.errors = self.errors + 1;
self.bump(src);
return try self.add(ast.Shape.Error, t, ast.NONE, ast.NONE);
}
}
+122
View File
@@ -0,0 +1,122 @@
unit scan;
import tok;
// A scanner over a byte slice. The position travels in a `&mut usize` beside
// the source rather than inside a struct with it, because a struct cannot hold
// a slice: a slice is a borrowed view and R4 keeps borrows out of aggregates.
fn is_space(c: u8) -> bool { return c == 32 or c == 9 or c == 13 or c == 10; }
fn is_digit(c: u8) -> bool { return c >= 48 and c <= 57; }
fn is_name_start(c: u8) -> bool {
if c >= 97 and c <= 122 { return true; }
if c >= 65 and c <= 90 { return true; }
return c == 95;
}
fn is_name_part(c: u8) -> bool {
return is_name_start(c) or is_digit(c);
}
const KEYWORDS: usize = 12;
fn is_keyword(word: []u8) -> bool {
if same(word, "unit") { return true; }
if same(word, "import") { return true; }
if same(word, "pub") { return true; }
if same(word, "fn") { return true; }
if same(word, "struct") { return true; }
if same(word, "enum") { return true; }
if same(word, "let") { return true; }
if same(word, "var") { return true; }
if same(word, "if") { return true; }
if same(word, "else") { return true; }
if same(word, "while") { return true; }
if same(word, "return") { return true; }
return false;
}
fn same(a: []u8, b: []u8) -> bool {
if a.n != b.n { return false; }
var i: usize = 0;
while i < a.n {
if a[i] != b[i] { return false; }
i = i + 1;
}
return true;
}
/// Step over anything that is not a token: spaces, newlines, and `//` to the
/// end of the line. `line` counts what was crossed so a token can say where it
/// came from.
fn skip_gaps(src: []u8, at: &mut usize, line: &mut usize) -> void {
while at.^ < src.n {
let c: u8 = src[at.^];
if c == 10 { line.^ = line.^ + 1; at.^ = at.^ + 1; }
else if is_space(c) { at.^ = at.^ + 1; }
else if c == 47 and at.^ + 1 < src.n and src[at.^ + 1] == 47 {
while at.^ < src.n {
if src[at.^] == 10 { break; }
at.^ = at.^ + 1;
}
}
else { break; }
}
}
pub fn next(src: []u8, at: &mut usize, line: &mut usize) -> tok.Token {
skip_gaps(src, at, line);
let start: usize = at.^;
let where: usize = line.^;
if start >= src.n {
return tok.Token{ kind: tok.Kind.End, from: start, len: 0, line: where };
}
let c: u8 = src[start];
if is_name_start(c) {
while at.^ < src.n {
if not is_name_part(src[at.^]) { break; }
at.^ = at.^ + 1;
}
let word: []u8 = src[start..at.^];
var kind: tok.Kind = tok.Kind.Name;
if is_keyword(word) { kind = tok.Kind.Keyword; }
return tok.Token{ kind: kind, from: start, len: at.^ - start,
line: where };
}
if is_digit(c) {
while at.^ < src.n {
if not is_digit(src[at.^]) { break; }
at.^ = at.^ + 1;
}
return tok.Token{ kind: tok.Kind.Number, from: start,
len: at.^ - start, line: where };
}
if c == 34 {
at.^ = at.^ + 1;
while at.^ < src.n {
if src[at.^] == 34 { break; }
if src[at.^] == 92 and at.^ + 1 < src.n { at.^ = at.^ + 1; }
at.^ = at.^ + 1;
}
if at.^ >= src.n {
return tok.Token{ kind: tok.Kind.Bad, from: start,
len: at.^ - start, line: where };
}
at.^ = at.^ + 1;
return tok.Token{ kind: tok.Kind.Text, from: start, len: at.^ - start,
line: where };
}
at.^ = at.^ + 1;
// Two-byte punctuation the language actually uses.
if at.^ < src.n {
let d: u8 = src[at.^];
if c == 45 and d == 62 { at.^ = at.^ + 1; }
else if c == 61 and d == 61 { at.^ = at.^ + 1; }
else if c == 33 and d == 61 { at.^ = at.^ + 1; }
else if c == 60 and d == 61 { at.^ = at.^ + 1; }
else if c == 62 and d == 61 { at.^ = at.^ + 1; }
else if c == 46 and d == 46 { at.^ = at.^ + 1; }
}
return tok.Token{ kind: tok.Kind.Punct, from: start, len: at.^ - start,
line: where };
}
+39
View File
@@ -0,0 +1,39 @@
unit tok;
// What the lexer produces. A token does not hold the text it came from: R4
// keeps borrows out of aggregate storage, so it records where in the source it
// starts and how long it is, and the source travels beside it.
pub enum Kind {
End,
Name,
Number,
Text,
Punct,
Keyword,
Bad,
}
pub struct Token {
pub kind: Kind,
pub from: usize,
pub len: usize,
pub line: usize,
}
pub fn text(src: []u8, t: Token) -> []u8 {
return src[t.from..t.from + t.len];
}
pub fn name_of(k: Kind) -> []u8 {
match k {
End => { return "end"; }
Name => { return "name"; }
Number => { return "number"; }
Text => { return "text"; }
Punct => { return "punct"; }
Keyword => { return "keyword"; }
Bad => { return "bad"; }
}
return "?";
}
+107
View File
@@ -0,0 +1,107 @@
// EXIT:0
// OUTPUT:unit demo
// OUTPUT:fn answer @2
// OUTPUT: let n = (+ 1 (* 2 3))
// OUTPUT: return n
// OUTPUT:fn greet @3
// OUTPUT: let s = "hi"
// OUTPUT: return
// OUTPUT:fn muddle @4
// OUTPUT: let x = <error>
// OUTPUT:nodes 17 errors 2
// OUTPUT:balanced
unit tree;
import std.io;
import std.sys;
import tok;
import ast;
import parse;
// The Ferro parser, written in Ferro.
//
// The lexer next to this file showed that a token can say where it came from
// instead of holding the text. A tree is the same idea one level up: a node
// cannot hold `^Node` children -- it has several and owns none of them once --
// and R4 keeps `&Node` out of aggregate storage. So the parser owns one array
// of nodes and every child is an index into it.
//
// Printing the expressions back in prefix form is the point of the test: it is
// the only way to see that `1 + 2 * 3` bound the way the grammar says.
const SOURCE: str = "unit demo;\nfn answer() { let n = 1 + 2 * 3; return n; }\nfn greet() { let s = \"hi\"; return; }\nfn muddle() { let x = ; }\n";
fn show_expr(p: &parse.Parser, src: []u8, i: usize) -> void {
if i == ast.NONE { return; }
let n: ast.Node = p.node(i);
match n.shape {
Binary => {
@print("({} ", src[n.from..n.from + n.len]);
show_expr(p, src, n.a);
@print(" ");
show_expr(p, src, n.b);
@print(")");
}
Error => { @print("<error>"); }
_ => { @print("{}", src[n.from..n.from + n.len]); }
}
return;
}
fn show_stmt(p: &parse.Parser, src: []u8, i: usize) -> void {
let n: ast.Node = p.node(i);
match n.shape {
Let => {
@print(" let {} = ", src[n.from..n.from + n.len]);
show_expr(p, src, n.b);
@print("\n");
}
Return => {
if n.a == ast.NONE { @print(" return\n"); }
else {
@print(" return ");
show_expr(p, src, n.a);
@print("\n");
}
}
_ => { @print(" <error>\n"); }
}
return;
}
fn show_item(p: &parse.Parser, src: []u8, i: usize) -> void {
let n: ast.Node = p.node(i);
match n.shape {
Fn => {
@print("fn {} @{}\n", src[n.from..n.from + n.len], n.line);
var s: usize = n.b;
while s != ast.NONE {
show_stmt(p, src, s);
s = p.node(s).next;
}
}
_ => { @print(" <error>\n"); }
}
return;
}
fn run() -> !void {
var p: parse.Parser = try parse.Parser.on(SOURCE);
let root: usize = try p.unit_decl(SOURCE);
let head: ast.Node = p.node(root);
@print("unit {}\n", SOURCE[head.from..head.from + head.len]);
var it: usize = head.a;
while it != ast.NONE {
show_item(&p, SOURCE, it);
it = p.node(it).next;
}
@print("nodes {} errors {}\n", p.count(), p.errors);
return;
}
fn main() -> i32 {
run() catch |e| { @print("out of memory\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked {}\n", sys.allocs() - sys.frees()); }
return 0;
}
+55
View File
@@ -0,0 +1,55 @@
// EXIT:0
// OUTPUT:walk 6 count 3
// OUTPUT:swapped 3 1
// OUTPUT:took 2 left 9
// OUTPUT:pop 1 then 9 empty -1
// OUTPUT:cleared 0 room 4
// OUTPUT:balanced
unit listmor;
import std.io;
import std.sys;
import std.list;
// The rest of the List surface a compiler needs: walk it, exchange two, move
// one out and leave something valid behind, take the last off, and empty it
// without going back to the allocator.
fn run() -> !void {
var xs: list.List(i32) = try list.List(i32).with_capacity(4);
try xs.push(1);
try xs.push(2);
try xs.push(3);
// `slice()` is a shared view derived from `self` (SPEC 5 R8(a)), which is
// what lets `for` walk it.
var sum: i32 = 0;
for x in xs.slice() { sum = sum + x.^; }
@print("walk {} count {}\n", sum, xs.count());
xs.swap(0, 2);
@print("swapped {} {}\n", xs.at(0), xs.at(2));
// SPEC 5 R7: what leaves a projection leaves a replacement behind.
let old: i32 = xs.take(1, 9);
@print("took {} left {}\n", old, xs.at(1));
let a: i32 = xs.pop() orelse -1;
let b: i32 = xs.pop() orelse -1;
let c: i32 = xs.pop() orelse -1;
let d: i32 = xs.pop() orelse -1;
@print("pop {} then {} empty {}\n", a, b, d);
try xs.push(7);
let room: usize = 4;
xs.clear();
@print("cleared {} room {}\n", xs.count(), room);
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}
+42
View File
@@ -0,0 +1,42 @@
// EXIT:0
// OUTPUT:count 20 sum 190 balanced
unit listuse;
import std.io;
import std.fmt;
import std.list;
import std.sys;
fn show(label: []u8, v: i32) -> void {
var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
let n: usize = fmt.fmt_i32(buf[..], v);
io.print(label);
io.print(" ");
io.print(buf[0..n]);
io.print(" ");
}
fn build() -> !i32 {
var xs: list.List(i32) = try list.List(i32).with_capacity(2);
var i: i32 = 0;
while i < 20 {
try xs.push(i);
i = i + 1;
}
var sum: i32 = 0;
var k: usize = 0;
while k < xs.count() {
sum = sum + xs.at(k);
k = k + 1;
}
show("count", xs.count() as i32);
show("sum", sum);
return sum;
}
fn main() -> i32 {
let sum: i32 = build() catch |e| { return 1; };
if sum != 190 { return 2; }
if sys.allocs() != sys.frees() { io.print("leaked\n"); return 3; }
io.print("balanced\n");
return 0;
}
+12
View File
@@ -0,0 +1,12 @@
// EXIT:1
unit logic;
fn side(v: i32) -> bool { return v > 0; }
fn main() -> i32 {
let a: bool = true and side(1);
let b: bool = false or side(2);
let c: bool = not side(0);
if a and b and c { return 1; }
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
// EXIT:55
unit loopcall;
fn add(a: i32, b: i32) -> i32 { return a + b; }
fn main() -> i32 {
var total: i32 = 0;
var i: i32 = 0;
while i < 10 {
total = total + add(i, 1);
i = i + 1;
}
return total;
}
+70
View File
@@ -0,0 +1,70 @@
// EXIT:0
// OUTPUT:count 5
// OUTPUT:fn 2 let 3 missing 0
// OUTPUT:grown 64
// OUTPUT:after 40
// OUTPUT:cleared 0 room 64 gone 0
// OUTPUT:refilled 3
// OUTPUT:balanced
unit maps;
import std.io;
import std.map;
import std.fmt;
import std.sys;
fn run() -> !void {
var seen: map.Map(i32) = try map.Map(i32).with_capacity(4);
try seen.put("unit", 1);
try seen.put("fn", 2);
try seen.put("let", 3);
try seen.put("struct", 4);
try seen.put("return", 5);
@print("count {}\n", seen.count_of());
@print("fn {} let {} missing {}\n", seen.get("fn", 0), seen.get("let", 0),
seen.get("nope", 0));
// Enough keys to make it grow more than once.
var buf: [8]u8 = undefined;
var i: usize = 0;
while i < 40 {
let n: usize = fmt.fmt_i32(buf[..], i as i32);
try seen.put(buf[0..n], (i as i32) + 100);
i = i + 1;
}
@print("grown {}\n", seen.room());
var found: usize = 0;
i = 0;
while i < 40 {
let n: usize = fmt.fmt_i32(buf[..], i as i32);
if seen.get(buf[0..n], 0) == (i as i32) + 100 { found = found + 1; }
i = i + 1;
}
@print("after {}\n", found);
// `clear` forgets every key and keeps the storage, which is what a scope
// that ends wants: hand the table to the next one without going back to
// the allocator. The room has to survive and the keys have to not.
let room: usize = seen.room();
seen.clear();
var gone: usize = 0;
i = 0;
while i < 40 {
let n: usize = fmt.fmt_i32(buf[..], i as i32);
if seen.has(buf[0..n]) { gone = gone + 1; }
i = i + 1;
}
@print("cleared {} room {} gone {}\n", seen.count_of(), room, gone);
// And it is usable again afterwards, reusing the same buffers.
try seen.put("a", 1);
try seen.put("b", 2);
try seen.put("c", 3);
@print("refilled {}\n", seen.count_of());
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() != sys.frees() { @print("leaked\n"); return 2; }
@print("balanced\n");
return 0;
}
+32
View File
@@ -0,0 +1,32 @@
// EXIT:0
// OUTPUT:a 5 b 200 c 44 d 9
// OUTPUT:e 5 f 1 g 65535
// OUTPUT:sum 253
unit narrow;
import std.io;
// How wide a store is belongs to the place, not to the value. An integer
// literal is `i32` until something narrower asks for it, so `let b: u8 = 200;`
// arrives at the store as four bytes going into one -- and writing four wipes
// out whatever the frame put beside it.
//
// Several locals of mixed width, next to each other, is what it takes to see
// it: each narrow store used to reach back over the one declared before it.
fn main() -> i32 {
let a: i32 = 5;
let b: u8 = 200;
let c: u8 = 44;
let d: i16 = 9;
@print("a {} b {} c {} d {}\n", a, b, c, d);
let e = 5; // no annotation: i32 (SPEC 4.1)
let f: i8 = 1;
let g: u16 = 65535;
@print("e {} f {} g {}\n", e, f as i32, g);
// And the values are still there after everything else was written.
@print("sum {}\n", (b as i32) + (c as i32) + (d as i32) + e - (a as i32));
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
// EXIT:42
unit optional;
fn pick(flag: bool) -> ?i32 {
if flag { return 42; }
return null;
}
fn main() -> i32 {
let a: ?i32 = pick(true);
let b: ?i32 = pick(false);
let x: i32 = a orelse 0;
let y: i32 = b orelse 0;
return x + y;
}

Some files were not shown because too many files have changed in this diff Show More