Files
coollang/lib/parser.ml
T
coolguyandClaude Opus 5 bb19a39e04 fuzz: 문법에서 문장을 만들어 파서에 먹인다 — 갈림 하나를 잡았다
저장소의 .cool 파일로만 대조하면 사람이 쓴 코드만 훑는다. 문법이 약속했는데
파서가 못 읽는 구석은 아무도 안 밟으면 드러나지 않는다.

lib/ebnf_gen.ml이 문법에서 문장을 만든다. 텍스트가 아니라 토큰 열을 만드는
이유는, 렉서의 줄바꿈 삽입을 거치면 문법이 허용해도 렉서가 만들 수 없는
문장이 생기는데 그건 파서의 잘못이 아니기 때문이다. 검사하려는 것은 문법과
파서 사이지 렉서가 아니다.

커버리지를 같이 잰다. 안 밟은 규칙은 시험되지 않은 규칙이므로, 통과했다는
말에 값이 없다. 현재 프로덕션 95개 전부를 밟고 거부 0건이다.

이 퍼저가 잡은 갈림 하나: 대입 왼쪽 제약. 문법은 expr_stmt = expr, ["=" expr]
로 적었는데 파서는 파싱 중에 "변수나 필드만"을 강제하고 있었다. 구문으로
가르면 ident 하나로 대입과 식이 갈리지 않아 LL(1)이 깨지므로, 제약을 이름
해소로 옮겼다. 파서는 이제 순수하게 구문만 본다.

만드는 과정에서 퍼저 자체의 함정도 하나 지났다. 처음엔 연료를 총 확장
횟수로 셌더니 선언 머리에서 다 써 버려 식과 문에 도달하지 못했고, 커버리지를
재기 전까지는 "3000개 통과"가 아무 뜻도 아니었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
2026-08-30 17:10:20 +09:00

858 lines
24 KiB
OCaml

(* 재귀 하강 파서. grammar.ebnf의 프로덕션 하나에 함수 하나로 대응한다.
LL(1)이므로 선읽기는 항상 한 토큰이고 backtracking은 없다.
문법과 얽히는 두 자리:
- NEWLINE 흡수는 문법에 { NEWLINE }으로 적힌 자리에서만 한다.
파서가 임의로 건너뛰지 않는다.
- struct 리터럴은 if/match/scope 머리에서 금지된다 (no_struct). *)
open Ast
type error = { pos : Token.pos; msg : string }
exception Error of error
type state = { toks : Token.t array; mutable i : int; mutable no_struct : bool }
let cur st = st.toks.(st.i)
let kind st = (cur st).Token.kind
let pos st = (cur st).Token.pos
let adv st = if st.i < Array.length st.toks - 1 then st.i <- st.i + 1
let err st msg = raise (Error { pos = pos st; msg })
let err_expect st what =
err st
(Printf.sprintf "%s이(가) 필요합니다 — %s 발견" what (Token.show_kind (kind st)))
let accept st k =
if kind st = k then (
adv st;
true)
else false
let expect st k what = if not (accept st k) then err_expect st what
let skip_nl st =
while kind st = Token.Newline do
adv st
done
(* 목록을 닫을 때 줄바꿈이 보이면 후행 콤마 누락이다. 일반 메시지보다
원인을 직접 말해준다. *)
let expect_close st k what =
if kind st = Token.Newline then err st "다중 줄 목록에는 후행 콤마가 필요합니다"
else expect st k what
let ident st what =
match kind st with
| Token.Ident n ->
adv st;
n
| _ -> err_expect st what
let with_struct_ok st f =
let saved = st.no_struct in
st.no_struct <- false;
let r = f () in
st.no_struct <- saved;
r
(* ------------------------------------------------------------------ *)
(* effect 절 *)
(* ------------------------------------------------------------------ *)
let parse_eff_name st =
let cap = ident st "capability 이름" in
expect st Token.Dot ".";
let meth = ident st "메서드 이름" in
{ cap; meth }
let parse_eff_set st =
expect st Token.LBrace "{";
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else
let n = parse_eff_name st in
skip_nl st;
if accept st Token.Comma then (
skip_nl st;
loop (n :: acc))
else (
skip_nl st;
List.rev (n :: acc))
in
let names = loop [] in
expect st Token.RBrace "}";
Eff_set names
let parse_eff_atom st =
match kind st with
| Token.LBrace -> parse_eff_set st
| Token.Ident n ->
adv st;
Eff_var n
| _ -> err_expect st "effect 변수 또는 { ... } 집합"
(* 결과 위치: 합집합 허용 *)
let parse_eff_result st =
expect st Token.Kw_effects "effects";
let rec loop acc =
let a = parse_eff_atom st in
if accept st Token.Pipe then loop (a :: acc) else List.rev (a :: acc)
in
loop []
(* 파라미터 위치: 합집합이 문법에 없다 *)
let parse_eff_param st =
expect st Token.Kw_effects "effects";
let a = parse_eff_atom st in
if kind st = Token.Pipe then
err st "파라미터 위치의 effects 절에는 합집합을 쓸 수 없습니다 (변수 단독 또는 리터럴 집합만 가능)";
a
(* ------------------------------------------------------------------ *)
(* 타입 *)
(* ------------------------------------------------------------------ *)
let rec parse_ty st =
match kind st with
| Token.Kw_affine ->
let p = pos st in
adv st;
expect st Token.Kw_fn "fn";
parse_fn_ty st true p
| Token.Kw_fn ->
let p = pos st in
adv st;
parse_fn_ty st false p
| Token.Ident n ->
let p = pos st in
adv st;
let modl, n =
if kind st = Token.Dot then (
adv st;
(Some n, ident st "타입 이름"))
else (None, n)
in
let args = if kind st = Token.LBracket then parse_targs st else [] in
T_named { modl; name = n; args; pos = p }
| _ -> err_expect st "타입"
and parse_fn_ty st affine p =
expect st Token.LParen "(";
let params =
if kind st = Token.RParen then []
else
let rec loop acc =
let t = parse_ty st in
if accept st Token.Comma then
if kind st = Token.RParen then List.rev (t :: acc) else loop (t :: acc)
else List.rev (t :: acc)
in
loop []
in
expect_close st Token.RParen ")";
let eff =
if kind st = Token.Kw_effects then Some (parse_eff_param st) else None
in
let ret = if accept st Token.Arrow then Some (parse_ty st) else None in
T_fn { affine; params; eff; ret; pos = p }
and parse_targs st =
expect st Token.LBracket "[";
let rec loop acc =
let a =
if kind st = Token.LBrace then TA_eff (parse_eff_set st)
else TA_ty (parse_ty st)
in
if accept st Token.Comma then
if kind st = Token.RBracket then List.rev (a :: acc) else loop (a :: acc)
else List.rev (a :: acc)
in
let args = loop [] in
expect_close st Token.RBracket "]";
args
(* ------------------------------------------------------------------ *)
(* 패턴 *)
(* ------------------------------------------------------------------ *)
let rec parse_pattern st =
let p = pos st in
match kind st with
| Token.Underscore ->
adv st;
P_wild p
| Token.Int s ->
adv st;
P_lit (L_int s, p)
| Token.Str s ->
adv st;
P_lit (L_str s, p)
| Token.Kw_true ->
adv st;
P_lit (L_bool true, p)
| Token.Kw_false ->
adv st;
P_lit (L_bool false, p)
| Token.Ident n -> (
adv st;
let modl, n =
if kind st = Token.Dot then (
adv st;
(Some n, ident st "생성자 이름"))
else (None, n)
in
if kind st = Token.LParen then (
adv st;
let rec loop acc =
let x = parse_pattern st in
if accept st Token.Comma then
if kind st = Token.RParen then List.rev (x :: acc)
else loop (x :: acc)
else List.rev (x :: acc)
in
let args = if kind st = Token.RParen then [] else loop [] in
expect_close st Token.RParen ")";
P_ctor { modl; name = n; args; pos = p })
else
match modl with
| Some _ -> P_ctor { modl; name = n; args = []; pos = p }
| None -> P_bind (n, p))
| _ -> err_expect st "패턴"
(* ------------------------------------------------------------------ *)
(* 식 *)
(* ------------------------------------------------------------------ *)
let rec parse_expr st = parse_or st
and parse_or st =
let lhs = ref (parse_and st) in
while kind st = Token.PipePipe do
let p = pos st in
adv st;
lhs := E_binary { op = B_or; lhs = !lhs; rhs = parse_and st; pos = p }
done;
!lhs
and parse_and st =
let lhs = ref (parse_cmp st) in
while kind st = Token.AmpAmp do
let p = pos st in
adv st;
lhs := E_binary { op = B_and; lhs = !lhs; rhs = parse_cmp st; pos = p }
done;
!lhs
and parse_cmp st =
let lhs = parse_add st in
let op =
match kind st with
| Token.EqEq -> Some B_eq
| Token.BangEq -> Some B_ne
| Token.Lt -> Some B_lt
| Token.Le -> Some B_le
| Token.Gt -> Some B_gt
| Token.Ge -> Some B_ge
| _ -> None
in
match op with
| None -> lhs
| Some op ->
let p = pos st in
adv st;
E_binary { op; lhs; rhs = parse_add st; pos = p }
and parse_add st =
let lhs = ref (parse_mul st) in
let rec go () =
let op =
match kind st with
| Token.Plus -> Some B_add
| Token.Minus -> Some B_sub
| _ -> None
in
match op with
| None -> ()
| Some op ->
let p = pos st in
adv st;
lhs := E_binary { op; lhs = !lhs; rhs = parse_mul st; pos = p };
go ()
in
go ();
!lhs
and parse_mul st =
let lhs = ref (parse_unary st) in
let rec go () =
let op =
match kind st with
| Token.Star -> Some B_mul
| Token.Slash -> Some B_div
| Token.Percent -> Some B_rem
| _ -> None
in
match op with
| None -> ()
| Some op ->
let p = pos st in
adv st;
lhs := E_binary { op; lhs = !lhs; rhs = parse_unary st; pos = p };
go ()
in
go ();
!lhs
and parse_unary st =
let p = pos st in
match kind st with
| Token.Bang ->
adv st;
E_unary { op = U_not; operand = parse_unary st; pos = p }
| Token.Minus ->
adv st;
E_unary { op = U_neg; operand = parse_unary st; pos = p }
| _ -> parse_postfix st
and parse_postfix st =
let e = ref (parse_primary st) in
let rec go () =
let p = pos st in
match kind st with
| Token.LParen ->
adv st;
let args =
if kind st = Token.RParen then []
else
with_struct_ok st (fun () ->
let rec loop acc =
let a = parse_expr st in
if accept st Token.Comma then
if kind st = Token.RParen then List.rev (a :: acc)
else loop (a :: acc)
else List.rev (a :: acc)
in
loop [])
in
expect_close st Token.RParen ")";
e := E_call { callee = !e; args; pos = p };
go ()
| Token.Dot ->
adv st;
let n = ident st "필드 또는 메서드 이름" in
e := E_field { obj = !e; name = n; pos = p };
go ()
| Token.LBracket ->
let args = with_struct_ok st (fun () -> parse_targs st) in
e := E_inst { callee = !e; args; pos = p };
go ()
| Token.Question ->
adv st;
e := E_try { inner = !e; pos = p };
go ()
| _ -> ()
in
go ();
!e
and parse_primary st =
let p = pos st in
match kind st with
| Token.Int s ->
adv st;
E_lit (L_int s, p)
| Token.Str s ->
adv st;
E_lit (L_str s, p)
| Token.Kw_true ->
adv st;
E_lit (L_bool true, p)
| Token.Kw_false ->
adv st;
E_lit (L_bool false, p)
| Token.LParen ->
adv st;
let e = with_struct_ok st (fun () -> parse_expr st) in
expect_close st Token.RParen ")";
e
| Token.LBracket ->
adv st;
let xs =
if kind st = Token.RBracket then []
else
with_struct_ok st (fun () ->
let rec loop acc =
let x = parse_expr st in
if accept st Token.Comma then
if kind st = Token.RBracket then List.rev (x :: acc)
else loop (x :: acc)
else List.rev (x :: acc)
in
loop [])
in
expect_close st Token.RBracket "]";
E_list (xs, p)
| Token.Kw_fn -> parse_closure st
| Token.Kw_if -> parse_if st
| Token.Kw_match -> parse_match st
| Token.Kw_scope ->
adv st;
let name = ident st "새 scope 이름" in
expect st Token.Eq "= (자식 scope의 부모를 명시해야 합니다)";
let parent = ident st "부모 scope 이름" in
let body = parse_block st in
E_scope { name; parent; body; pos = p }
| Token.Ident n ->
adv st;
if (not st.no_struct) && kind st = Token.LBrace then
let fields = parse_struct_lit_fields st in
E_struct { name = n; fields; pos = p }
else E_ident (n, p)
| _ -> err_expect st "식"
and parse_struct_lit_fields st =
expect st Token.LBrace "{";
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else begin
let n = ident st "필드 이름" in
expect st Token.Colon ":";
let v = with_struct_ok st (fun () -> parse_expr st) in
skip_nl st;
if accept st Token.Comma then (
skip_nl st;
loop ((n, v) :: acc))
else (
skip_nl st;
List.rev ((n, v) :: acc))
end
in
let fields = loop [] in
expect st Token.RBrace "}";
fields
and parse_closure st =
let p = pos st in
expect st Token.Kw_fn "fn";
expect st Token.LParen "(";
let params =
if kind st = Token.RParen then []
else
let rec loop acc =
let n = ident st "파라미터 이름" in
let t = if accept st Token.Colon then Some (parse_ty st) else None in
if accept st Token.Comma then
if kind st = Token.RParen then List.rev ((n, t) :: acc)
else loop ((n, t) :: acc)
else List.rev ((n, t) :: acc)
in
loop []
in
expect_close st Token.RParen ")";
let eff =
if kind st = Token.Kw_effects then Some (parse_eff_param st) else None
in
let ret = if accept st Token.Arrow then Some (parse_ty st) else None in
let body = parse_block st in
E_closure
{
cl_params = params;
cl_eff = eff;
cl_ret = ret;
cl_body = body;
cl_pos = p;
}
and parse_if st =
let p = pos st in
expect st Token.Kw_if "if";
let saved = st.no_struct in
st.no_struct <- true;
let cond = parse_expr st in
st.no_struct <- saved;
let then_ = parse_block st in
let else_ =
if accept st Token.Kw_else then
if kind st = Token.Kw_if then Some (parse_if st)
else Some (E_block (parse_block st))
else None
in
E_if { cond; then_; else_; pos = p }
and parse_match st =
let p = pos st in
expect st Token.Kw_match "match";
let saved = st.no_struct in
st.no_struct <- true;
let scrutinee = parse_expr st in
st.no_struct <- saved;
expect st Token.LBrace "{";
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else begin
let ap = pos st in
let pat = parse_pattern st in
if kind st = Token.Kw_if then
err st "match 가드는 v0에 없습니다 (분기 본문에서 if를 쓰십시오)";
expect st Token.FatArrow "=>";
let body =
with_struct_ok st (fun () ->
if kind st = Token.LBrace then E_block (parse_block st)
else parse_expr st)
in
skip_nl st;
if accept st Token.Comma then (
skip_nl st;
loop ({ arm_pat = pat; arm_body = body; arm_pos = ap } :: acc))
else (
skip_nl st;
List.rev ({ arm_pat = pat; arm_body = body; arm_pos = ap } :: acc))
end
in
let arms = loop [] in
expect st Token.RBrace "}";
E_match { scrutinee; arms; pos = p }
(* ------------------------------------------------------------------ *)
(* 문과 블록 *)
(* ------------------------------------------------------------------ *)
and parse_block st =
let p = pos st in
expect st Token.LBrace "{";
let saved = st.no_struct in
st.no_struct <- false;
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else begin
let s = parse_stmt st in
if kind st = Token.Newline then (
skip_nl st;
loop (s :: acc))
else if kind st = Token.RBrace then List.rev (s :: acc)
else err st "문 끝에 줄바꿈이 필요합니다"
end
in
let stmts = loop [] in
expect st Token.RBrace "}";
st.no_struct <- saved;
{ stmts; block_pos = p }
and parse_stmt st =
let p = pos st in
match kind st with
| Token.Kw_let ->
adv st;
let mut_ = accept st Token.Kw_mut in
let pat = parse_pattern st in
let ty = if accept st Token.Colon then Some (parse_ty st) else None in
expect st Token.Eq "=";
let value = parse_expr st in
S_let { mut_; pat; ty; value; pos = p }
| Token.Kw_return ->
adv st;
let value =
if kind st = Token.Newline || kind st = Token.RBrace then None
else Some (parse_expr st)
in
S_return { value; pos = p }
| _ ->
let e = parse_expr st in
if accept st Token.Eq then
(* 대입 왼쪽에 무엇이 올 수 있는지는 구문이 아니라 이름 해소가
판정한다. 구문으로 가르면 ident 하나로 대입과 식이 갈리지 않아
문법이 LL(1)이 아니게 된다 (grammar.ebnf의 expr_stmt). *)
S_assign { place = e; value = parse_expr st; pos = p }
else S_expr e
(* ------------------------------------------------------------------ *)
(* 선언 *)
(* ------------------------------------------------------------------ *)
let parse_gen_params st =
expect st Token.LBracket "[";
let rec loop acc =
let p = pos st in
let n = ident st "타입 또는 effect 파라미터 이름" in
let is_eff =
if accept st Token.Colon then (
expect st Token.Kw_effects "effects";
true)
else false
in
let g = { gp_name = n; gp_effect = is_eff; gp_pos = p } in
if accept st Token.Comma then
if kind st = Token.RBracket then List.rev (g :: acc) else loop (g :: acc)
else List.rev (g :: acc)
in
let gs = loop [] in
expect_close st Token.RBracket "]";
gs
let parse_params st =
if kind st = Token.RParen then []
else
let rec loop acc =
let p = pos st in
let own = accept st Token.Kw_own in
let mut_ = accept st Token.Kw_mut in
let name = ident st "파라미터 이름" in
expect st Token.Colon ":";
let ty = parse_ty st in
let prm =
{ p_own = own; p_mut = mut_; p_name = name; p_ty = ty; p_pos = p }
in
if accept st Token.Comma then
if kind st = Token.RParen then List.rev (prm :: acc)
else loop (prm :: acc)
else List.rev (prm :: acc)
in
loop []
(* fn_decl = "fn" ident [gen] "(" [params] ")" {NL}
[eff_result {NL}] ["->" type {NL}] [block]
{ NEWLINE } 흡수 위치는 문법에 적힌 그대로다. *)
let parse_fn_decl st ~allow_body =
let p = pos st in
expect st Token.Kw_fn "fn";
let name = ident st "함수 이름" in
let gen = if kind st = Token.LBracket then parse_gen_params st else [] in
expect st Token.LParen "(";
let params = parse_params st in
expect_close st Token.RParen ")";
skip_nl st;
let eff =
if kind st = Token.Kw_effects then (
let e = parse_eff_result st in
skip_nl st;
Some e)
else None
in
let ret =
if accept st Token.Arrow then (
let t = parse_ty st in
skip_nl st;
Some t)
else None
in
let body =
if allow_body && kind st = Token.LBrace then Some (parse_block st) else None
in
{
fn_name = name;
fn_gen = gen;
fn_params = params;
fn_eff = eff;
fn_ret = ret;
fn_body = body;
fn_pos = p;
}
let parse_struct st ~pub ~copyable =
let p = pos st in
expect st Token.Kw_struct "struct";
let name = ident st "타입 이름" in
let gen = if kind st = Token.LBracket then parse_gen_params st else [] in
expect st Token.LBrace "{";
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else begin
let fp = pos st in
let n = ident st "필드 이름" in
expect st Token.Colon ":";
let t = parse_ty st in
let f = { f_name = n; f_ty = t; f_pos = fp } in
skip_nl st;
if accept st Token.Comma then (
skip_nl st;
loop (f :: acc))
else (
skip_nl st;
List.rev (f :: acc))
end
in
let fields = loop [] in
expect st Token.RBrace "}";
I_struct { pub; copyable; name; gen; fields; pos = p }
let parse_enum st ~pub =
let p = pos st in
expect st Token.Kw_enum "enum";
let name = ident st "타입 이름" in
let gen = if kind st = Token.LBracket then parse_gen_params st else [] in
expect st Token.LBrace "{";
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else begin
let vp = pos st in
let n = ident st "variant 이름" in
let args =
if accept st Token.LParen then begin
let rec go acc =
let t = parse_ty st in
if accept st Token.Comma then
if kind st = Token.RParen then List.rev (t :: acc)
else go (t :: acc)
else List.rev (t :: acc)
in
let ts = if kind st = Token.RParen then [] else go [] in
expect_close st Token.RParen ")";
ts
end
else []
in
let v = { v_name = n; v_args = args; v_pos = vp } in
skip_nl st;
if accept st Token.Comma then (
skip_nl st;
loop (v :: acc))
else (
skip_nl st;
List.rev (v :: acc))
end
in
let variants = loop [] in
expect st Token.RBrace "}";
I_enum { pub; name; gen; variants; pos = p }
let parse_capability st ~pub =
let p = pos st in
expect st Token.Kw_capability "capability";
let name = ident st "capability 이름" in
expect st Token.LBrace "{";
skip_nl st;
let rec loop acc =
if kind st = Token.RBrace then List.rev acc
else
let m = parse_fn_decl st ~allow_body:false in
skip_nl st;
loop (m :: acc)
in
let methods = loop [] in
expect st Token.RBrace "}";
I_capability { pub; name; methods; pos = p }
let parse_item st =
let p = pos st in
match kind st with
| Token.Kw_import ->
adv st;
let path =
match kind st with
| Token.Str s ->
adv st;
s
| _ -> err_expect st "모듈 경로 문자열"
in
expect st Token.Kw_as "as";
let alias = ident st "별칭" in
I_import { path; alias; pos = p }
| Token.Kw_reexport ->
adv st;
let name = ident st "재수출할 이름" in
I_reexport { name; pos = p }
| _ -> (
let pub = accept st Token.Kw_pub in
match kind st with
| Token.Kw_fn -> I_fn { pub; decl = parse_fn_decl st ~allow_body:true }
| Token.Kw_copyable ->
adv st;
parse_struct st ~pub ~copyable:true
| Token.Kw_struct -> parse_struct st ~pub ~copyable:false
| Token.Kw_enum -> parse_enum st ~pub
| Token.Kw_capability -> parse_capability st ~pub
| Token.Kw_const ->
adv st;
let name = ident st "상수 이름" in
expect st Token.Colon ":";
let ty = parse_ty st in
expect st Token.Eq "=";
let value = parse_expr st in
I_const { pub; name; ty; value; pos = p }
| _ -> err_expect st "선언 (fn, struct, enum, capability, const)")
(* ------------------------------------------------------------------ *)
(* 오류 복구 *)
(* *)
(* 항목 단위로만 회복한다. 오류가 난 선언은 통째로 버리고 다음 선언에서 *)
(* 다시 시작한다 — 문 단위로 더 잘게 회복하려 하면 파서가 추측을 하게 되고, *)
(* 틀린 추측은 없는 오류를 지어낸다. 한 항목에 오류 하나가 상한이라는 것은 *)
(* 정직한 한계이지 숨길 것이 아니다. *)
(* *)
(* 동기화 지점: 중괄호 깊이 0이고, 줄 첫머리이며, 선언을 시작할 수 있는 토큰. *)
(* 세 조건이 다 필요하다. 본문 안의 fn을 새 항목으로 오인하면 그 뒤가 전부 *)
(* 어긋난다. *)
let item_starts =
[
Token.Kw_import;
Token.Kw_reexport;
Token.Kw_pub;
Token.Kw_fn;
Token.Kw_struct;
Token.Kw_enum;
Token.Kw_capability;
Token.Kw_const;
Token.Kw_copyable;
]
let at_line_start st = st.i > 0 && st.toks.(st.i - 1).Token.kind = Token.Newline
let sync st =
let depth = ref 0 in
let fin = ref false in
while not !fin do
match kind st with
| Token.Eof -> fin := true
| Token.LBrace ->
incr depth;
adv st
| Token.RBrace ->
decr depth;
adv st
| k ->
if !depth <= 0 && at_line_start st && List.mem k item_starts then
fin := true
else adv st
done
let parse_module st =
skip_nl st;
let errors = ref [] in
let rec loop acc =
if kind st = Token.Eof then List.rev acc
else
match parse_item st with
| it ->
skip_nl st;
loop (it :: acc)
| exception Error e ->
errors := e :: !errors;
let before = st.i in
sync st;
(* 진행 보장. 같은 자리에서 다시 실패하면 무한 루프다. *)
if st.i = before then adv st;
skip_nl st;
loop acc
in
let items = loop [] in
({ items }, List.rev !errors)
let parse_all (tokens : Token.t list) : modul * error list =
let st = { toks = Array.of_list tokens; i = 0; no_struct = false } in
parse_module st
let parse (tokens : Token.t list) : modul =
match parse_all tokens with m, [] -> m | _, e :: _ -> raise (Error e)
let parse_result tokens =
match parse_all tokens with m, [] -> Ok m | _, e :: _ -> Error e