설명서와 파서가 어긋나 있었다. 그냥 어긋난 게 아니라, 그 설명서를 안 읽고
"else if가 안 된다"고 마찰 보고서에 잘못 적었다 — 설명서로서 제 역할을 한
번도 못 했다는 뜻이다.
lib/ebnf.ml: EBNF를 데이터로 읽는다. nullable, FIRST, FOLLOW를 계산하고
LL(1) 충돌을 보고한다. 매개변수 프로덕션(name<p>)을 지원한다 — expr_ns와
목록을 복제 없이 적기 위한 것이다.
문법 첫머리의 "설계 제약: LL(1)"은 지금까지 사람의 주장이었다. 기계로 재니
충돌 18건이 나왔다. 세 부류였다:
- 구조적 3건: stmt(assign/expr), primary(ident/struct_lit), pattern —
파서는 왼쪽 인수분해를 손으로 했는데 문법에 안 적혀 있었다
- 후행 콤마 9건: X , { "," , X } , [ "," ]는 콤마를 본 시점에 갈리지 않는다.
우재귀로 다시 적었다
- 줄바꿈 흡수 6건: 어느 쪽이 먹어도 파스 트리가 같다. greedy 규칙을 표기에
명시하고 그렇게 해소되는 것만 따로 분류한다
지금은 진짜 충돌 0건이고, 테스트가 이를 고정한다.
병렬 조사에서 나온 드리프트도 모두 반영했다:
- named_type, name_pattern에 한정 이름(Shapes.Shape, Shapes.Circle)
- name_pattern이 인자 0개와 괄호 없는 한정 생성자를 받는다
- string_lit의 이스케이프
- cap_method의 gen_params (파서가 이미 허용하고 있었다)
- field/variant/field_init/arm 사이의 콤마는 필수다
- stmt 사이의 NEWLINE도 필수다 — 선택적으로 적었더니 그것 하나가 LL(1)
충돌 셋을 만들었다
- { NEWLINE }을 전부 [ NEWLINE ]으로. 렉서가 연속 줄바꿈을 만들 수 없다
expr_ns는 산문 주석이었고 파서 상태 플래그로 구현돼 있었다. 매개변수
프로덕션으로 형식화해 문맥자유가 됐다.
렉서 쪽: Token.next_kind가 모든 토큰을 한 번씩 잇는다. 나열이 빠지면
컴파일러가 지적한다. 문법 문서의 키워드 표와 줄 끝 판정 목록은 이제
lib/lexical_doc.ml이 거기서 생성하고 테스트가 대조한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
grammar.ebnf의 어휘 절을 구현한다. 렉서는 선읽기를 요구하지 않고,
문법과 얽히는 유일한 부분인 NEWLINE 삽입은 can_end_statement 하나로
판정한다. 모든 토큰이 위치를 들고 다닌다 — 진단 품질이 헌법급이므로
나중에 붙이지 않는다.
샘플을 실제로 렉싱해 함정을 하나 잡았다. effects 절이 줄 끝에 오면 "}"가
값 종료 토큰이라 NEWLINE이 삽입되어 다음 줄의 "->"와 끊긴다(Go ASI와 같은
형태). 렉서에 문맥을 주는 대신 — 렉서 피드백은 철학 2가 배제한다 —
시그니처 머리의 흡수 위치를 프로덕션에 명시적으로 적어 닫았다. 파서가
임의로 건너뛰는 것이 아니라 문법에 적힌 자리에서만 흡수한다.
다중 줄 목록의 후행 콤마 필수도 함께 명시(콤마로 끝난 줄은 NEWLINE을
만들지 않으므로 목록이 자연히 이어진다).
cool check는 어휘 분석을 돌리되 통과했다고 말하지 않는다. 파이프라인의
나머지가 없는 이상 그 파일은 검사된 것이 아니다. 디버깅용 cool tokens 추가.
테스트 30건: 키워드, 두 글자 연산자 우선, NEWLINE 삽입 6가지 경우,
리터럴과 이스케이프, 오류 7종과 오류 위치, 그리고 samples/*.cool 전체가
어휘 분석을 통과하는지.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E