전부 구현 쪽이었다. SPEC 은 §6.2(체이닝·단항 뒤 as), §5 R9(asm), §6.1·§7.3
(extern, 빈 enum/error)을 이미 옳게 적고 있었고 구현만 따라가지 않았다.
const A: i32 = r(); 전역 초기값에 호출. lowering 이 조용히 버리고 0
static A: i32 = r(); 같은 것을 저장소 0 으로
true == false == true 비교 체이닝
-x as u32 괄호 없이 단항 뒤 as
asm { } unsafe 밖에서
extern fn f(); ABI 문자열 없이
extern "stdcall" fn f(); c 아닌 ABI
extern "c" fn f() { } extern 에 본문
fn f() -> i32; extern 아닌데 본문 없음
enum E { } error E { } 빈 선언
FRONT-01/02 는 SPEC 에도 규칙이 없었다. §7.1 에 넣었다 -- 전역의 바이트는
이미지에 들어가므로 초기값이 실행될 순간이 없다. 리터럴과 다른 const, 배리언트,
error.Name, 그리고 그것들에 대한 연산까지가 허용된다.
-x as T 와 비교 체이닝을 잡으려면 괄호가 트리에 남아야 해서 FE_NODE_PAREN 을
두었다. 파싱 뒤에는 -x as T 와 -(x as T) 가 같은 트리다.
그리고 0b1010 이 0, 0o17 이 0 이었다. 10진으로 읽다가 b 에서 멈춰 0 을 내는데
0 도 숫자라 아무도 눈치채지 못한다. 값 계산 두 군데를 고쳤다.
262/262, 40/40.
구현 셋, SPEC 다섯. 어느 쪽이 틀렸는지는 항목마다 따로 판정했다.
구현:
- ~ 를 넣었다. ^ 는 xor 과 .^ 가 가져가서 비트 NOT 이 자기 철자를 못 갖고
있었다. 렉서·파서·검사·lowering(전부 1 과의 xor).
- 전역 static/var 는 타입을 적는다. 다른 유닛이 읽는 링커 심볼이라 초기값의
생김새에 타입을 맡기면 그쪽이 보는 것이 달라진다. const 는 그대로 추론한다.
- error code 는 정수 리터럴 하나다. 식이면 중복도 예약된 0 도 검사할 수 없다.
SPEC:
- 최상위 comptime if 를 뺐다. comptime 조건은 타입 술어뿐인데(§7.5) 유닛
바깥에는 바인딩된 타입 파라미터가 없어 물어볼 것이 없다. §11 v0.2.
- 타입 이름은 [binding.]Name 이다. import 가 마지막 segment 를 바인딩하므로
점 둘 이상은 만들어질 수 없는데 문법이 unit_path 를 쓰고 있었다.
- catch 의 EBNF 가 §4.6 보다 넓었다. 값을 주는 짧은 형태와 에러를 받는 블록
형태 둘로 나눠 적었다.
- | 와 ^ 를 한 단계로 둔 것을 쪼갰다. 합치면 a | b ^ c 가 좌결합으로
(a|b)^c 가 되어 C 에서 온 사람을 속인다. 구현이 C 순서로 옳았다.
- 마지막 필드 쉼표 생략을 명세에 적었다. enum 은 이미 허용하고 있었다.
그리고 tests/run.py 가 마커를 진단 스트림에만 맞춘다. --dump-ast 모드에서는
AST 덤프가 stdout 으로 먼저 나와서 parse/ fixture 는 마커를 쓸 수 없었다.
249/249, 39/39.
Name{...} 과 binding.Name{...} 만 있고 호출 뒤의 { 를 아무도 받지 않았다.
그래서 타입 인자를 명시한 리터럴이 파싱되지 않았고, 제네릭의 인스턴스는
Self{...} 나 생성자 함수로만 만들 수 있었다.
파서는 호출 뒤의 { 를 struct 리터럴로 받고, 체커는 그것을 타입 표기와 똑같은
resolver 에 넘긴다 -- 같은 철자니 같은 답이어야 한다. { 의 애매함은 이미
Name{...} 에 있던 것과 같고 같은 guard 가 정리한다.
hold.Cell(i32){ v: 7 } 다른 유닛의 제네릭
hold.Handle(Node){ raw: 3 } 본문에서 T 를 안 쓰는 것
Boxed(i32){ v: 9 } 이 유닛의 제네릭
미사용 타입 파라미터는 그대로 둔다. typed handle 이 바로 그 모양이고,
Handle(Node) 와 Handle(Kind) 가 실제로 다른 타입이라는 것은 badphant 가
거부로 고정한다.
233/233, 34/34.
x[a..b] 는 인덱스가 아니라 포인터와 길이를 만든다. 양쪽 끝을 -- 서로에 대해,
그리고 있는 것에 대해 -- 검사한 뒤에 포인터를 만든다. 유효한 범위의 빈
슬라이스는 괜찮고 끝보다 늦게 시작하는 것은 아니다.
파서가 x[a] 와 x[a..] 를 구분하지 못했다. 둘 다 b 만 있고 c 가 없어서
모양이 같았다. '..' 가 있었으면 노드에 표시한다.
정수 출력이 된다: fizz 12345 -678
io.Writer 는 핸들 하나짜리 enum 이다. 참조도 컨텍스트 포인터도 담지 않으므로
Copy 이고 자유롭게 오간다 (SPEC 5 R8). fmt 는 sink 를 소유하지 않는다 --
호출자가 버퍼를 주고 앞에서 몇 바이트가 쓰였는지 돌려받는다.
lowering 에 추가: enum 변이 상수, match, 정수 캐스트, 문자열 이스케이프.
프론트엔드 정밀도 하나: 항상 빠져나가는 분기의 상태를 병합하지 않는다. 그
분기가 소비한 값이 그 분기를 지나지 않은 경로에서도 소비된 것처럼 보였다.
fmt_i32 가 이것 때문에 못 쓰였다.
extern "c" 이름은 유닛 접두사를 붙이지 않는다. 링커가 이미 아는 이름이라는
것이 그 선언의 요점이다.
run.py 199/199, exec.py 11/11.
세그먼트 주소 지정은 x86 리얼모드에만 있는 개념이고, 평평한 주소 공간을 가진
다른 32비트 프로세서에는 대응물이 없다. 영구 제외다.
usize/isize 는 타깃의 포인터 폭이며 특정 비트 수를 약속하지 않는다. 오늘
32비트지만 u32 와 자동 변환되지 않는다. bits16 에서 배울 것은 '32로 정하자'가
아니라 '폭이 언어 의미론으로 새어나가지 않게 하자'이고, 그것이 나중에 다른
폭의 타깃을 여는 유일한 장치다.
타깃이 하나이므로 레이아웃에서 pointer_bits 분기가 전부 사라졌다. 인터럽트
핸들러와 공유 상태는 v0.2 로 내렸다 -- 문법은 아직 파싱되지만 명세의 약속은
아니다.
타입 인자를 바인딩한 상태로 선언을 인스턴스마다 한 번씩 검사한다. 바인딩된
이름은 그냥 그 인자 타입이므로 본문, 필드 타입, 시그니처가 모두 같은 규칙으로
풀린다. 인스턴스 정체성은 선언 유닛 + 선언 + 인자 철자다.
찾은 버그 셋:
- 파서가 comptime 파라미터의 이름을 'comptime' 이라는 키워드에서 가져갔다.
타입 파라미터 이름이 전부 comptime 이 되어 아무것도 바인딩되지 않았다.
- 인스턴스 이름이 중첩마다 길어져서, 깊은 사슬에서 잘린 이름끼리 충돌해
재귀가 깊이 제한에 닿기 전에 조용히 멈췄다. 길어지면 인자를 일련번호로
적어 정체성을 유지한다.
- 순서 비교 연산자가 피연산자 타입을 보지 않아 구조체끼리 비교해도 통과했다.
제네릭과 무관한 기존 구멍이다.
fixture 셋이 명세와 어긋나 있어 명세를 따랐다. badbody 와 badop 은 호출 지점을
primary error 로 기대했지만 SPEC 9 는 본문의 연산이 primary 이고 호출에는
'instantiated here' note 를 붙이라고 한다. okscope 는 제네릭 본문이 호출자의
이름을 본다고 기대했지만 SPEC 9 는 정의 유닛에서 해석한다 -- badscope 로 옮기고
이유를 적었다.
188/188.
유닛마다 FeCheck 를 새로 만들면 타입 문맥도 유닛마다 따로 생겨서 유닛 경계를
넘는 이름을 볼 수가 없었다. 검사기가 빌드 전체를 맡고, 모든 유닛의 선언을
등록한 뒤에 어느 본문이든 보기 시작한다.
스코프와 심볼과 타입은 현재 유닛보다 오래 살아야 하므로 AST 아레나가 아니라
검사기 자신의 아레나에서 잡는다. 이름이 같아도 유닛이 다르면 다른 타입이므로
nominal 타입은 선언한 유닛으로도 구분한다.
pub 은 파서가 버리고 있었다. 이제 FE_NODE_PUB 으로 남긴다.
Units start here, with the part that needs no import graph: what a unit is
called and where it must live.
The parser only ever read a single identifier after `unit` and `import`, so
`unit game.main;` and `import std.io;` were syntax errors -- which is why the
dotted fixtures failed at the semicolon. It now reads a dotted path and stores
it canonically, dots included, since that spelling is the unit's identity
everywhere else. `import a.b as c;` parses too, with the alias on the node.
resolve.c is the new pass between parsing and checking, for the questions that
span files. It carries SPEC 8.1 so far: each path segment is ASCII lowercase,
starts with a letter, continues with letters, digits or underscore, and is at
most eight characters; and the dotted path must match the source path it was
read from, so game.world.map has to come from game/world/map.fe. The source
side is folded to lowercase before comparing, because a case-insensitive host
must not let two spellings become two units.
That rule then applied to the fixtures, which were not obeying it: 57 declared
a unit name unrelated to their file, left over from the milestone directories,
and eight had names too long to be legal. Both are now aligned -- the rule is
worth having only if the tree follows it.
units: badupper, badlong and unitbad pass. 138 -> 146 of 188. The rest of
units/ needs the import graph, which is the next piece: resolution, cycles,
bindings and visibility.