pc and testing -> seperate files

This commit is contained in:
2025-09-03 13:13:31 +02:00
parent dea16908a6
commit afd552bd09
5 changed files with 552 additions and 437 deletions

407
pc.ml Normal file
View File

@@ -0,0 +1,407 @@
type severity = Style | Warning | Error
type parse_error = {
severity: severity;
pos: int;
message: string;
expected: string list option;
found: string option;
other_case: parse_error option;
}
type src_info = {
source: string;
path: string;
}
type cursor = { source: string; pos: int }
type parse_result = {
cursor: cursor;
errors: parse_error list
}
type 'a parsr = cursor -> 'a * parse_result
type off2lres = {
line: int; (** 0-indexed *)
col: int; (** 0-indexed *)
lstr: string;
};;
exception Parser_Cant_Recover of parse_result
exception Parser_No_Progress of cursor
let num_occ s f =
let num = ref 0 in
s |> Seq.iter begin fun x ->
if f x then
num := !num + 1
end;
!num
;;
let rec off2l (s:string) (p:int) =
let e = match String.index_from_opt s p '\n' with
Some(lb) -> lb
| None -> String.length s
in
match String.rindex_from_opt s p '\n' with
Some(lb) -> {
line = num_occ (Seq.take lb (String.to_seq s)) (fun x -> x = '\n');
col = p - lb;
lstr = String.sub s (lb + 1) (e - lb - 1) }
| None -> {
line = 0;
col = p;
lstr = String.sub s 0 e }
;;
let str_before s c =
match String.index_opt s c with
Some(x) -> String.sub s 0 (x - 1)
| None -> s
;;
let limitw len s =
let s = str_before s '\n' in
if String.length s > len then
(String.sub s 0 (len - 3)) ^ "..."
else s
;;
let rec pp_err (s:src_info) out (e:parse_error) =
let open Format in
fprintf out "[%s] @[" begin match e.severity with
Style -> "style"
| Warning -> "warn "
| Error -> "error"
end;
fprintf out "%s@]\n" e.message;
let where = off2l s.source e.pos in
fprintf out " in @[%s@]\n" s.path;
fprintf out " at %i:%i\n\n" where.line where.col;
fprintf out "%3d: %s\n" where.line where.lstr;
fprintf out " ";
for _ = 0 to where.col - 2 do
fprintf out " "
done;
fprintf out "^\n";
begin match e.expected with
Some([]) -> fprintf out "expected: nothing\nbut "
| Some(li) -> fprintf out "expected: @[%a@]\nbut "
(Format.pp_print_list Format.pp_print_text) li
| None -> fprintf out "unexpected\n"
end;
fprintf out "got '@[";
begin match e.found with
| Some(x) -> fprintf out "%s" @@ limitw 22 x
| None -> fprintf out "something else"
end;
fprintf out "@]'\n";
begin match e.other_case with
Some(c) -> begin
fprintf out "\n ^^^\n";
fprintf out " \\\ === OR tried alternative parser === \\\\\\\n";
fprintf out " \\/\n\n";
pp_err s out c
end
| None -> () end
;;
let pp_errli s out e =
let open Format in
let first = ref true in
e |> List.iter begin fun e ->
if not !first then begin
fprintf out "\n"
end;
first := false;
pp_err s out e
end
;;
let head c =
if String.length c.source > 0 then
Some(String.get c.source 0)
else
None;;
let create src = { source = src; pos = 0 };;
let expected c str =
{
severity = Error;
pos = c.pos;
message = "";
expected = Some([str]);
found = Some(c.source);
other_case = None;
};;
let seek num c =
{ source =
String.sub c.source num ((String.length c.source) - num);
pos = c.pos + num };;
let merge a b =
{ cursor = b.cursor;
errors = a.errors @ b.errors };;
let ok c = { cursor = c; errors = [] }
let put_err err res = { res with errors = res.errors @ [err] }
let err c e = put_err e (ok c)
;;
let single_inline_white c =
match head c with
Some(' ') |
Some('\b') |
Some('\t') ->
((), ok (seek 1 c))
| _ -> raise (Parser_Cant_Recover (err c (expected c "inline whitespace")))
;;
let single_white c =
match head c with
Some(' ') |
Some('\b') |
Some('\t') |
Some('\r') |
Some('\n') ->
((), ok (seek 1 c))
| _ -> raise (Parser_Cant_Recover (err c (expected c "whitespace")))
;;
let any c =
match head c with
Some(x) -> (x, ok (seek 1 c))
| None -> raise (Parser_Cant_Recover (err c (expected c "any")))
;;
let rec repeat p c =
try
let (v, res) = p c in
if res.cursor.pos = c.pos then
raise (Parser_No_Progress c)
else
let (v2, res2) = repeat p res.cursor in
(v :: v2, merge res res2)
with Parser_Cant_Recover _ ->
([], ok c)
;;
let many p c =
let (av, ar) = p c in
let (bv, br) = repeat p (ar.cursor) in
(av :: bv, merge ar br)
;;
let rec until endp p c =
try
let (_, res) = endp c in
([], res)
with Parser_Cant_Recover _ ->
let (v, res) = p c in
if res.cursor.pos = c.pos then
raise (Parser_No_Progress c)
else
let (v2, res2) = until endp p res.cursor in
(v :: v2, merge res res2)
;;
let map fn p c =
let (v, res) = p c in
((fn v), res);;
let set v p c =
let (_, res) = p c in
(v, res);;
let opt p c =
try
map (fun x -> Some(x)) p c
with Parser_Cant_Recover _ ->
(None, ok c)
;;
let inv p c =
let fail = (try
let (_, r) = p c in
Some(r)
with Parser_Cant_Recover _ ->
None) in
let rec ouch r =
match r with
[] -> None
| head :: tail ->
let h = (match head.expected with
Some(x) -> Some( List.map (fun x -> "not " ^ x) x )
| None -> None
) in
let t = ouch tail in
match h, t with
Some(x), Some(y) -> Some(x @ y)
| Some(x), None -> Some(x)
| None, Some(y) -> Some(y)
| None, None -> None
in
match fail with
Some(r) -> raise (Parser_Cant_Recover (err c {
severity = Error;
pos = c.pos;
message = "inverted parser matched";
expected = ouch r.errors;
found = Some(c.source);
other_case = None
}))
| None -> ((), ok c)
;;
let also a b c =
let (av, ar) = a c in
let (_, br) = b c in
(av, merge br ar);;
exception Recursive_Parser_Used_Before_Created
let recursive (prod:'a->'c-> 'v * 'r) (c:cursor) =
let future = ref None in
let par = prod (fun c -> match !future with
None -> raise Recursive_Parser_Used_Before_Created
| Some(p) -> p c) in
future := Some(par);
par c
;;
let just str c =
if String.starts_with ~prefix:str c.source then
(str, ok (seek (String.length str) c))
else
raise (Parser_Cant_Recover (err c (expected c str)))
;;
let ex_end c =
if (String.length c.source) > 0 then
raise (Parser_Cant_Recover (err c (expected c "end of file")))
else
((), ok c)
;;
let chain a b c =
let ar = a c in
let br = b (snd ar).cursor in
((fst ar, fst br), merge (snd ar) (snd br))
;;
let ignore_then a b c =
let ar = a c in
let br = b (snd ar).cursor in
((fst br), merge (snd ar) (snd br))
;;
let then_ignore a b c =
let ar = a c in
let br = b (snd ar).cursor in
((fst ar), merge (snd ar) (snd br))
;;
let nd_of3 a b c =
then_ignore (ignore_then a b) c;;
let outer_of3 a b c =
chain (then_ignore a b) c;;
let ignore p c =
let (_, r) = p c in
((), r);;
let either a b c =
try a c
with Parser_Cant_Recover ar ->
try b c
with Parser_Cant_Recover br ->
raise (Parser_Cant_Recover {
cursor = ar.cursor;
errors = [{ (List.hd ar.errors) with
other_case = Some(List.hd br.errors) }]
})
;;
let match_hd label fn c =
match head c with
Some(x) when fn x ->
(x, ok (seek 1 c))
| _ -> raise (Parser_Cant_Recover (err c (expected c label)))
;;
let hd_oneof label li =
match_hd label (fun x ->
Seq.exists (fun t->t=x) li);;
let digitch =
(hd_oneof "digit"
(String.to_seq "0123456789"));;
let digit =
map (fun x -> Char.code x - Char.code '0')
digitch;;
let lower =
hd_oneof "lowercase letter"
(String.to_seq "abcdefghijklmnopqrstuvwxyz");;
let upper =
hd_oneof "uppercase letter"
(String.to_seq "ABCDEFGHIJKLMNOPQRSTUVWXYZ");;
let alpha = either lower upper;;
let alnum = either alpha digitch;;
let sign c =
match head c with
Some '+' -> ( 1, ok (seek 1 c))
| Some '-' -> (-1, ok (seek 1 c))
| _ -> ( 1, ok (seek 1 c))
;;
let p_uint =
map (fun digits ->
List.fold_right (fun x acc -> acc*10 + x) (List.rev digits) 0
) (many digit)
;;
let p_int =
chain sign p_uint
|> map (fun (sign,d) -> sign * d)
;;
let p_uflt =
let fract =
map (fun digits ->
List.fold_right
(fun x (acc,d) -> (acc +. (Float.of_int x) /. d), d *. 10.0 )
(List.rev digits)
(0.0,10.0)
) (many digit)
in outer_of3 p_int (just ".") fract
|> map (fun (a,(b,_)) -> (Float.of_int a) +. b)
;;
let p_flt =
chain sign p_uflt
|> map (fun (sign,d) -> Float.of_int sign *. d)
;;
let doparse f (src:src_info) parsr =
let (v,r) = try
let (v,r) = parsr (create src.source) in
(Some(v), r)
with Parser_Cant_Recover r ->
(None, r)
in
pp_errli src f r.errors;
v
;;