panic: panic/Never와 내장 테스트 — 문법을 먼저 고치고 대조 장치가 파서를 지적했다
순서가 요점이다. 문법에 test_decl과 panic_expr을 넣고 파서는 안 고친 채로
대조 장치를 돌렸더니 즉시 잡혔다:
문장 300개 중 파서가 거부한 것 140개
[1] 선언 (fn, struct, enum, capability, const)이(가) 필요합니다 — test 발견
파서를 따라가게 하니 다시 0건. 문법과 구현이 어긋나는 상태가 관측 가능한
것이 되었다는 뜻이다.
panic:
- 키워드다. prelude가 없어 함수로 두면 쓸 때마다 import해야 한다
- effect가 아니다. 경계 검사 하나에 {Panic}이 호출자 전부로 전염되면
effect 절은 신호가 아니라 잡음이 된다
- Never는 어떤 타입 자리에도 놓인다. 없으면 panic을 match 팔에서 못 쓴다
- 언어 수준 recover 없음. 되감기 없음. 자원 해제 여부는 열어둔다
- 0으로 나누기, assert 실패가 이 하나로 모인다
test:
- 파라미터가 없어 capability를 받을 수 없고, 만들 문법도 없다. 그래서
effect-free임이 증명된다 — 관례가 아니라 검사다. 시험해 보니 실제로
"테스트는 effect를 수행할 수 없습니다"로 거부한다
- 일반 코드와 같은 타입/effect/move 검사를 받는다
- interface hash에서 제외 — 테스트를 고쳤다고 downstream이 재검사되면 안 된다
- 격리는 런타임의 일이다. 하나가 죽어도 나머지는 돈다
assert는 std/test.cool에 coollang으로 쓰였다 — panic 위의 설탕임이 코드로
보이고, std에서 본문이 있는 첫 함수가 됐다. 그 바람에 std/런타임 양방향
테스트가 걸렸고(본문 있는 함수에 런타임 구현을 요구했다), 그 구분을 넣었다.
samples/app/config.cool에 첫 테스트 넷.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+48
-30
@@ -88,7 +88,8 @@ let tokenize (src : string) : (tok * int) array =
|
||||
let start = !i + 1 in
|
||||
incr i;
|
||||
while !i < n && src.[!i] <> quote do
|
||||
if src.[!i] = '\n' then raise (Error { line = !line; msg = "단말이 닫히지 않았습니다" });
|
||||
if src.[!i] = '\n' then
|
||||
raise (Error { line = !line; msg = "단말이 닫히지 않았습니다" });
|
||||
incr i
|
||||
done;
|
||||
if !i >= n then raise (Error { line = !line; msg = "단말이 닫히지 않았습니다" });
|
||||
@@ -115,7 +116,7 @@ let tokenize (src : string) : (tok * int) array =
|
||||
emit t;
|
||||
incr i
|
||||
in
|
||||
(match c with
|
||||
match c with
|
||||
| '=' -> single T_eq
|
||||
| ';' -> single T_semi
|
||||
| ',' -> single T_comma
|
||||
@@ -130,9 +131,7 @@ let tokenize (src : string) : (tok * int) array =
|
||||
| '<' -> single T_lt
|
||||
| '>' -> single T_gt
|
||||
| _ ->
|
||||
raise
|
||||
(Error
|
||||
{ line = !line; msg = Printf.sprintf "알 수 없는 문자 %c" c }))
|
||||
raise (Error { line = !line; msg = Printf.sprintf "알 수 없는 문자 %c" c })
|
||||
end
|
||||
done;
|
||||
emit T_eof;
|
||||
@@ -147,12 +146,10 @@ type state = { toks : (tok * int) array; mutable p : int }
|
||||
let cur st = fst st.toks.(st.p)
|
||||
let line st = snd st.toks.(st.p)
|
||||
let adv st = if st.p < Array.length st.toks - 1 then st.p <- st.p + 1
|
||||
|
||||
let fail st msg = raise (Error { line = line st; msg })
|
||||
|
||||
let eat st t what =
|
||||
if cur st = t then adv st
|
||||
else fail st (Printf.sprintf "%s이(가) 필요합니다" what)
|
||||
if cur st = t then adv st else fail st (Printf.sprintf "%s이(가) 필요합니다" what)
|
||||
|
||||
(* alt := seq { "|" seq } *)
|
||||
let rec parse_alt st =
|
||||
@@ -265,7 +262,8 @@ let parse (src : string) : t =
|
||||
done;
|
||||
List.rev !rules
|
||||
|
||||
let parse_result src = match parse src with r -> Ok r | exception Error e -> Error e
|
||||
let parse_result src =
|
||||
match parse src with r -> Ok r | exception Error e -> Error e
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
(* 조회 *)
|
||||
@@ -280,7 +278,8 @@ let undefined (g : t) : string list =
|
||||
let seen = Hashtbl.create 32 in
|
||||
let rec walk = function
|
||||
| Ref n -> if not (List.mem n defined) then Hashtbl.replace seen n ()
|
||||
| RefArg (n, _) -> if not (List.mem n defined) then Hashtbl.replace seen n ()
|
||||
| RefArg (n, _) ->
|
||||
if not (List.mem n defined) then Hashtbl.replace seen n ()
|
||||
| Term _ -> ()
|
||||
| Seq xs | Alt xs -> List.iter walk xs
|
||||
| Opt e | Rep e -> walk e
|
||||
@@ -309,7 +308,8 @@ let unreachable (g : t) ~(start : string) : string list =
|
||||
in
|
||||
List.iter (fun r -> walk r.body) g;
|
||||
List.filter_map
|
||||
(fun r -> if r.name = start || Hashtbl.mem used r.name then None else Some r.name)
|
||||
(fun r ->
|
||||
if r.name = start || Hashtbl.mem used r.name then None else Some r.name)
|
||||
g
|
||||
|
||||
let rec show_expr = function
|
||||
@@ -326,7 +326,9 @@ and show_paren e =
|
||||
match e with Alt _ | Seq _ -> "( " ^ show_expr e ^ " )" | _ -> show_expr e
|
||||
|
||||
let show_rule r =
|
||||
let ps = if r.params = [] then "" else "<" ^ String.concat ", " r.params ^ ">" in
|
||||
let ps =
|
||||
if r.params = [] then "" else "<" ^ String.concat ", " r.params ^ ">"
|
||||
in
|
||||
r.name ^ ps ^ " = " ^ show_expr r.body ^ " ;"
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
@@ -356,13 +358,13 @@ let expand (g : t) : t =
|
||||
| Term _ -> e
|
||||
(* 매개변수 이름이 그대로 참조된 자리도 인자로 바꾼다: list<item>의 item *)
|
||||
| Ref n -> ( match List.assoc_opt n env with Some v -> Ref v | None -> e)
|
||||
| RefArg (n, a) ->
|
||||
| RefArg (n, a) -> (
|
||||
let a = match List.assoc_opt a env with Some v -> v | None -> a in
|
||||
(match by_name_arg n a with
|
||||
match by_name_arg n a with
|
||||
| Some r when r.params <> [] ->
|
||||
let key = (n, a) in
|
||||
if not (Hashtbl.mem out (mangle n a)) && not (List.mem key !queue) then
|
||||
queue := key :: !queue;
|
||||
if (not (Hashtbl.mem out (mangle n a))) && not (List.mem key !queue)
|
||||
then queue := key :: !queue;
|
||||
Ref (mangle n a)
|
||||
| _ -> Ref n)
|
||||
| Seq xs -> Seq (List.map (subst env) xs)
|
||||
@@ -398,8 +400,9 @@ let expand (g : t) : t =
|
||||
else
|
||||
Hashtbl.fold
|
||||
(fun k v acc ->
|
||||
if String.length k > String.length r.name
|
||||
&& String.sub k 0 (String.length r.name + 1) = r.name ^ "<"
|
||||
if
|
||||
String.length k > String.length r.name
|
||||
&& String.sub k 0 (String.length r.name + 1) = r.name ^ "<"
|
||||
then v :: acc
|
||||
else acc)
|
||||
out []
|
||||
@@ -429,7 +432,8 @@ let is_token a n = SS.mem n a.tokens || find a.rules n = None
|
||||
|
||||
let rec nullable_expr a = function
|
||||
| Term _ -> false
|
||||
| Ref n -> if is_token a n then false else Hashtbl.find_opt a.nullable n = Some true
|
||||
| Ref n ->
|
||||
if is_token a n then false else Hashtbl.find_opt a.nullable n = Some true
|
||||
| RefArg (n, x) -> nullable_expr a (Ref (mangle n x))
|
||||
| Seq xs -> List.for_all (nullable_expr a) xs
|
||||
| Alt xs -> List.exists (nullable_expr a) xs
|
||||
@@ -438,11 +442,13 @@ let rec nullable_expr a = function
|
||||
|
||||
let rec first_expr a = function
|
||||
| Term s -> SS.singleton s
|
||||
| Ref n ->
|
||||
| Ref n -> (
|
||||
if is_token a n then SS.singleton n
|
||||
else ( match Hashtbl.find_opt a.first n with Some s -> s | None -> SS.empty)
|
||||
else
|
||||
match Hashtbl.find_opt a.first n with Some s -> s | None -> SS.empty)
|
||||
| RefArg (n, x) -> first_expr a (Ref (mangle n x))
|
||||
| Alt xs -> List.fold_left (fun acc x -> SS.union acc (first_expr a x)) SS.empty xs
|
||||
| Alt xs ->
|
||||
List.fold_left (fun acc x -> SS.union acc (first_expr a x)) SS.empty xs
|
||||
| Opt x | Rep x -> first_expr a x
|
||||
| Except (x, _) -> first_expr a x
|
||||
| Seq xs ->
|
||||
@@ -476,7 +482,11 @@ let analyze ?(tokens = []) (g : t) : analysis =
|
||||
Hashtbl.replace a.nullable r.name true;
|
||||
changed := true);
|
||||
let f = first_expr a r.body in
|
||||
let old = match Hashtbl.find_opt a.first r.name with Some s -> s | None -> SS.empty in
|
||||
let old =
|
||||
match Hashtbl.find_opt a.first r.name with
|
||||
| Some s -> s
|
||||
| None -> SS.empty
|
||||
in
|
||||
if not (SS.equal f old) then (
|
||||
Hashtbl.replace a.first r.name (SS.union old f);
|
||||
changed := true))
|
||||
@@ -498,10 +508,12 @@ let nullable a name = Hashtbl.find_opt a.nullable name = Some true
|
||||
type follow_env = {
|
||||
a : analysis;
|
||||
fol : (string, SS.t) Hashtbl.t;
|
||||
mutable deps : (string * string) list; (* (n, owner): follow n ⊇ follow owner *)
|
||||
mutable deps : (string * string) list;
|
||||
(* (n, owner): follow n ⊇ follow owner *)
|
||||
}
|
||||
|
||||
let get_fol e n = match Hashtbl.find_opt e.fol n with Some s -> s | None -> SS.empty
|
||||
let get_fol e n =
|
||||
match Hashtbl.find_opt e.fol n with Some s -> s | None -> SS.empty
|
||||
|
||||
let rec collect e owner expr (cont : SS.t) (cont_end : bool) =
|
||||
match expr with
|
||||
@@ -576,13 +588,18 @@ let conflicts ?(tokens = []) ?(greedy = []) (g : t) : conflict list =
|
||||
c_tokens = toks;
|
||||
c_detail = detail;
|
||||
(* 선택/반복만 greedy로 해소된다. 대안(Alt) 충돌은 못 덮는다 *)
|
||||
c_greedy = kind <> "선택" && List.for_all (fun t -> List.mem t greedy) toks;
|
||||
c_greedy =
|
||||
kind <> "선택" && List.for_all (fun t -> List.mem t greedy) toks;
|
||||
}
|
||||
:: !out
|
||||
in
|
||||
let rec walk r expr (cont : SS.t) (cont_end : bool) =
|
||||
let cont_full =
|
||||
if cont_end then SS.union cont (match Hashtbl.find_opt fol r.name with Some s -> s | None -> SS.empty)
|
||||
if cont_end then
|
||||
SS.union cont
|
||||
(match Hashtbl.find_opt fol r.name with
|
||||
| Some s -> s
|
||||
| None -> SS.empty)
|
||||
else cont
|
||||
in
|
||||
match expr with
|
||||
@@ -592,12 +609,13 @@ let conflicts ?(tokens = []) ?(greedy = []) (g : t) : conflict list =
|
||||
let n = List.length xs in
|
||||
for i = 0 to n - 1 do
|
||||
for j = i + 1 to n - 1 do
|
||||
let fi = first_expr a (List.nth xs i) and fj = first_expr a (List.nth xs j) in
|
||||
let fi = first_expr a (List.nth xs i)
|
||||
and fj = first_expr a (List.nth xs j) in
|
||||
let inter = SS.inter fi fj in
|
||||
if not (SS.is_empty inter) then
|
||||
add r "선택" (SS.elements inter)
|
||||
(Printf.sprintf "%d번째와 %d번째 대안이 같은 토큰으로 시작합니다: %s / %s"
|
||||
(i + 1) (j + 1)
|
||||
(Printf.sprintf "%d번째와 %d번째 대안이 같은 토큰으로 시작합니다: %s / %s" (i + 1)
|
||||
(j + 1)
|
||||
(show_expr (List.nth xs i))
|
||||
(show_expr (List.nth xs j)))
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user