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
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
(deps
|
||||
(glob_files %{workspace_root}/samples/*.cool)
|
||||
(glob_files %{workspace_root}/std/*.cool)
|
||||
(glob_files %{workspace_root}/docs/*.ebnf)
|
||||
(glob_files %{workspace_root}/samples/app/*)
|
||||
(glob_files %{workspace_root}/samples/modules/*.cool)
|
||||
(glob_files %{workspace_root}/samples/run/*.cool)))
|
||||
|
||||
@@ -1306,3 +1306,72 @@ let () =
|
||||
(Printf.sprintf "런타임 구현 %s에 std 선언이 있다" name)
|
||||
(List.mem name declared))
|
||||
Interp.implemented
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 문법 파일 *)
|
||||
(* *)
|
||||
(* docs/grammar.ebnf는 이제 문서가 아니라 기계가 읽는 소스다. 여기서 *)
|
||||
(* 검사하는 것 셋: *)
|
||||
(* 1. 기계가 읽을 수 있는가 *)
|
||||
(* 2. 정의되지 않은 이름이 토큰 부류뿐인가 *)
|
||||
(* 3. LL(1)인가 — 문법 첫머리의 주장이 여기서 검증된다 *)
|
||||
(* ------------------------------------------------------------------ *)
|
||||
|
||||
let () =
|
||||
let src =
|
||||
let ic = open_in_bin "../docs/grammar.ebnf" in
|
||||
let n = in_channel_length ic in
|
||||
let s = really_input_string ic n in
|
||||
close_in ic;
|
||||
s
|
||||
in
|
||||
match Ebnf.parse_result src with
|
||||
| Error e ->
|
||||
Printf.printf " (문법 파일 %d행: %s)\n" e.line e.msg;
|
||||
check "grammar.ebnf를 읽을 수 있다" false
|
||||
| Ok g ->
|
||||
check "grammar.ebnf를 읽을 수 있다" true;
|
||||
check "프로덕션이 충분히 있다" (List.length g > 50);
|
||||
(* 어휘 층의 이름만 정의 없이 참조될 수 있다 *)
|
||||
let allowed = [ "NEWLINE"; "char"; "digit"; "letter" ] in
|
||||
let undef = Ebnf.undefined g in
|
||||
List.iter
|
||||
(fun n ->
|
||||
check
|
||||
(Printf.sprintf "grammar.ebnf: %s는 정의되었거나 토큰 부류다" n)
|
||||
(List.mem n allowed))
|
||||
undef;
|
||||
check "grammar.ebnf에 죽은 프로덕션이 없다"
|
||||
(Ebnf.unreachable g ~start:"module" = []);
|
||||
let g = Ebnf.expand g in
|
||||
let tokens = allowed @ [ "ident"; "int_lit"; "string_lit" ] in
|
||||
let real =
|
||||
List.filter
|
||||
(fun (c : Ebnf.conflict) -> not c.c_greedy)
|
||||
(Ebnf.conflicts ~tokens ~greedy:[ "NEWLINE" ] g)
|
||||
in
|
||||
List.iter
|
||||
(fun (c : Ebnf.conflict) ->
|
||||
Printf.printf " (LL(1) 충돌 %s:%d [%s] %s — %s)\n" c.c_rule c.c_line
|
||||
c.c_kind
|
||||
(String.concat " " c.c_tokens)
|
||||
c.c_detail)
|
||||
real;
|
||||
check "grammar.ebnf는 LL(1)이다" (real = [])
|
||||
|
||||
(* 문법 문서의 어휘 절은 코드에서 생성된다. 두 곳에 손으로 적힌 목록은
|
||||
어긋난다 — 이 프로젝트가 그것으로 한 번 데었다. *)
|
||||
let () =
|
||||
let src =
|
||||
let ic = open_in_bin "../docs/grammar.ebnf" in
|
||||
let n = in_channel_length ic in
|
||||
let s = really_input_string ic n in
|
||||
close_in ic;
|
||||
s
|
||||
in
|
||||
let block = Lexical_doc.render () in
|
||||
check "문법 문서의 어휘 절이 token.ml과 일치한다" (has_sub src block);
|
||||
if not (has_sub src block) then begin
|
||||
print_endline " 현재 코드가 만드는 블록:";
|
||||
print_endline block
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user