Ferro 파서를 Ferro 로, 그리고 그것이 드러낸 네 가지

렉서 다음은 파서다. 노드는 한 배열에 살고 자식은 그 안의 인덱스다 -- 노드는
^Node 를 들 수 없고(여럿이며 한 번씩 소유하지 않는다) &Node 도 들 수 없다(R4).
인덱스는 둘 다 아니다. 소스도 필드가 아니라 매 단계에 같이 다닌다.

  unit demo / fn answer @2 / let n = (+ 1 (* 2 3)) / return n / balanced

전위 표기로 다시 찍는 것이 시험의 요점이다. 1 + 2 * 3 이 어떻게 묶였는지는
그렇게만 보인다.

쓰면서 나온 컴파일러 버그 넷:

1. 다른 유닛의 타입을 필드로 쓰면 그 필드 타입이 영영 UNKNOWN 이었다. 필드
   해석이 유닛마다 선언 직후에 돌아서, 아직 선언되지 않은 유닛의 타입을 찾다
   실패하고 그 답을 굳혔다. 이제 모든 유닛이 선언을 마친 뒤에 한 번 푼다.

2. 그리고 그 해석은 타입을 선언한 유닛에서 해야 한다. 필드 타입은 그 유닛의
   import 로 쓰였는데 아무 유닛에서나 풀고 있었다. 타입 계층에 enter/leave
   콜백을 두고 체커가 그 자리로 데려간다.

3. cycle_state 를 재귀 검사와 크기 계산이 같이 썼다. 첫 번째가 보는 중인 구조체
   가 두 번째에게는 다 끝난 것으로 보여서, 필드가 하나뿐인 것처럼 1 바이트로
   자리를 잡았다 -- Parser 가 그래서 자기 토큰을 밟았다. layout_state 로 나눴다.

4. 다른 유닛의 상수(ast.NONE)를 lowering 이 필드 접근으로 봤다. 체커가 이미
   링크 이름을 붙여두었으니 그것이 있으면 전역이다.

그리고 R1 을 실제로 지키게 했다: 소유자를 놓으면 그것이 가진 것도 놓는다.
전에는 자기 drop 이 있거나 자기가 owned 일 때만이어서, drop 을 가진 타입을
필드로 담은 구조체는 그것을 놓을 방법이 없었다(drop 은 손으로 못 부른다).
이제 release_at 이 drop 을 부르고 필드로 내려간다. 그 덕에 List/Arena/Map 의
drop 이 전부 필요 없어져서 지웠다 -- 버퍼가 owned 이니 R1 이 알아서 한다.

221/221, 29/29.
This commit is contained in:
2026-08-17 12:49:53 +09:00
parent 8c4e80e246
commit e84f892147
15 changed files with 582 additions and 58 deletions
+35
View File
@@ -284,6 +284,9 @@ void enter_unit(FeCheck *c, unsigned index)
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)
{
@@ -301,6 +304,8 @@ void fe_check_init(FeCheck *c, FeBuild *build, FeDiags *diags,
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;
@@ -354,6 +359,36 @@ FeType *unit_type(FeCheck *c, FeUnit *u, const char *name)
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)