Files
coollang/lib/lexical_doc.ml
T
coolguyandClaude Opus 5 bde940eda3 grammar: 문법 문서를 기계가 읽는 소스로 — LL(1)이 처음으로 검증된다
설명서와 파서가 어긋나 있었다. 그냥 어긋난 게 아니라, 그 설명서를 안 읽고
"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
2026-08-30 17:03:25 +09:00

59 lines
2.2 KiB
OCaml

(* 문법 문서의 어휘 절을 코드에서 생성한다.
키워드 표와 "줄을 끝낼 수 있는 토큰" 목록은 지금까지 token.ml과
grammar.ebnf 양쪽에 손으로 적혀 있었다. 그런 목록은 어긋난다 —
실제로 이 프로젝트의 문법 문서 전체가 그렇게 어긋났다.
여기서 생성하고 테스트가 대조하므로, 이제 토큰을 추가하면 문서가 낡거나
빌드가 깨진다. 사람이 두 곳을 맞출 의무가 없다. *)
let display (k : Token.kind) =
match k with
| Token.Ident _ -> Some "ident"
| Token.Int _ -> Some "int_lit"
| Token.Str _ -> Some "string_lit"
| Token.Newline | Token.Eof -> None
| _ -> Some ("\"" ^ Token.show_kind k ^ "\"")
let is_keyword (k : Token.kind) = Token.keyword (Token.show_kind k) = Some k
let keywords () =
List.filter_map
(fun k -> if is_keyword k then Some ("\"" ^ Token.show_kind k ^ "\"") else None)
Token.all_kinds
let statement_enders () =
List.filter_map
(fun k -> if Token.can_end_statement k then display k else None)
Token.all_kinds
let begin_mark = "(* 여기부터 lib/token.ml에서 생성됩니다 — 손으로 고치지 마십시오 *)"
let end_mark = "(* 생성 끝 *)"
(* 한 줄이 길어지지 않게 접는다. 구분자는 줄 끝에 남겨 이어짐이 보이게 한다 *)
let wrap ~indent items =
let rec go acc line = function
| [] -> List.rev (line :: acc)
| x :: rest ->
if line = indent then go acc (line ^ x) rest
else if String.length line + String.length x + 3 > 72 then
go ((line ^ " |") :: acc) (indent ^ x) rest
else go acc (line ^ " | " ^ x) rest
in
String.concat "\n" (go [] indent items)
let render () =
String.concat "\n"
[
begin_mark;
" * 줄을 끝낼 수 있는 토큰. 줄의 마지막 토큰이 이 중 하나이면 그 줄 끝에";
" * NEWLINE이 삽입된다. Token.can_end_statement가 원본이다.";
" *";
wrap ~indent:" * " (statement_enders ());
" *";
" * 키워드. 이름으로 쓸 수 없다. Token.keyword가 원본이다.";
" *";
wrap ~indent:" * " (keywords ());
" " ^ end_mark;
]