Merge branch 'master' into 'main'
Merge master into main See merge request coolguy/doslang-mirror!1
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/clangd.json
|
||||
CompileFlags:
|
||||
Add:
|
||||
- -xc
|
||||
- -std=c89
|
||||
- -Ifec/src
|
||||
+30
@@ -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/
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`에 있다.
|
||||
@@ -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 재설계가 아니다.
|
||||
@@ -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 이후 별도 검토.
|
||||
@@ -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 줄이다.
|
||||
@@ -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로 확인한다.
|
||||
@@ -0,0 +1,44 @@
|
||||
# SPEC–fec 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`다.
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
src\check.c(2795): Error! E1118: ***FATAL*** No such file or directory
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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. */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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. */
|
||||
@@ -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
|
||||
@@ -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 --- */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(); }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# 제네릭 인스턴스 리터럴
|
||||
|
||||
`Name(args){...}` 와 `binding.Name(args){...}` 로 제네릭의 인스턴스를 짓는다.
|
||||
전에는 `Self{...}` 나 생성자 함수로만 만들 수 있었다.
|
||||
|
||||
`Handle(T)` 는 `T` 를 본문에서 쓰지 않는다 -- typed handle 의 자연스러운 모양이고,
|
||||
`Handle(Node)` 와 `Handle(Kind)` 를 갈라놓는 것 말고는 하는 일이 없다. 그 둘이
|
||||
실제로 다른 타입이라는 것은 `generic/badphant.fe` 가 거부로 고정한다.
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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))` 로 나오는 것
|
||||
말고는 우선순위가 맞았는지 볼 방법이 없다.
|
||||
@@ -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 "?";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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 "?";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user