OCaml parser combinator that compiles to direct recursive descent
5

Configure Feed

Select the types of activity you want to include in your feed.

ppx_combin / src / check.ml
6.0 kB 161 lines
1open Ir 2 3type error = 4 | Left_recursion of { loc : loc; name : string; path : string list } 5 | Ambiguous_alt of { loc : loc } 6 7let left_recursive_path env names expr = 8 let rec nullable = function 9 | Pure _ -> true 10 | Fail _ -> false 11 | Satisfy _ -> false 12 | Any _ -> false 13 | Eof _ -> true 14 | Token _ -> false 15 | Tokens _ -> false 16 | OneOf _ -> false 17 | NoneOf _ -> false 18 | Bind { p; _ } -> nullable p 19 | Map { p; _ } -> nullable p 20 | Apply { pf; px; _ } -> nullable pf && nullable px 21 | SeqLeft { p; q; _ } -> nullable p && nullable q 22 | SeqRight { p; q; _ } -> nullable p && nullable q 23 | Alt { p; q; _ } -> nullable p || nullable q 24 | Attempt { p; _ } -> nullable p 25 | Cut _ -> true 26 | Lookahead _ -> true 27 | NotFollowedBy _ -> true 28 | Many _ -> true 29 | Some_ { p; _ } -> nullable p 30 | Optional _ -> true 31 | Option _ -> true 32 | SepBy _ -> true 33 | SepBy1 { p; sep; _ } -> nullable p && nullable sep 34 | EndBy _ -> true 35 | EndBy1 { p; sep; _ } -> nullable p && nullable sep 36 | ManyTill _ -> true 37 | Count _ -> false 38 | Between { open_; close; p; _ } -> nullable open_ && nullable p && nullable close 39 | ChainL _ -> true 40 | ChainL1 { p; _ } -> nullable p 41 | ChainR _ -> true 42 | ChainR1 { p; _ } -> nullable p 43 | Choice { ps; _ } -> List.exists nullable ps 44 | Label { p; _ } -> nullable p 45 | Fix _ -> false 46 | Var { name; _ } -> 47 (match List.assoc_opt name env with 48 | Some e -> nullable e 49 | None -> false) 50 in 51 let rec check path = function 52 | Var { name; _ } when List.mem name names -> 53 Some (List.rev (name :: path)) 54 | Var { name; _ } -> 55 (match List.assoc_opt name env with 56 | Some e -> check (name :: path) e 57 | None -> None) 58 | Bind { p; _ } -> check path p 59 | Map { p; _ } -> check path p 60 | Apply { pf; px; _ } -> 61 (match check path pf with 62 | Some _ as r -> r 63 | None -> if nullable pf then check path px else None) 64 | SeqLeft { p; q; _ } -> 65 (match check path p with 66 | Some _ as r -> r 67 | None -> if nullable p then check path q else None) 68 | SeqRight { p; q; _ } -> 69 (match check path p with 70 | Some _ as r -> r 71 | None -> if nullable p then check path q else None) 72 | Alt { p; q; _ } -> 73 (match check path p with 74 | Some _ as r -> r 75 | None -> check path q) 76 | Attempt { p; _ } -> check path p 77 | Lookahead { p; _ } -> check path p 78 | NotFollowedBy { p; _ } -> check path p 79 | Label { p; _ } -> check path p 80 | Many { p; _ } -> check path p 81 | Some_ { p; _ } -> check path p 82 | Optional { p; _ } -> check path p 83 | Option { p; _ } -> check path p 84 | SepBy { p; _ } -> check path p 85 | SepBy1 { p; _ } -> check path p 86 | EndBy { p; _ } -> check path p 87 | EndBy1 { p; _ } -> check path p 88 | ManyTill { p; _ } -> check path p 89 | Count { p; _ } -> check path p 90 | Between { open_; _ } -> check path open_ 91 | ChainL { p; _ } -> check path p 92 | ChainL1 { p; _ } -> check path p 93 | ChainR { p; _ } -> check path p 94 | ChainR1 { p; _ } -> check path p 95 | Choice { ps; _ } -> List.find_map (check path) ps 96 | Fix _ -> None 97 | Pure _ | Fail _ | Satisfy _ | Any _ | Eof _ | Token _ | Tokens _ 98 | OneOf _ | NoneOf _ | Cut _ -> None 99 in 100 check [] expr 101 102let check_left_recursion env names expr = 103 match left_recursive_path env names expr with 104 | Some path -> Some (Left_recursion { loc = loc_of_expr expr; name = List.hd names; path }) 105 | None -> None 106 107let is_wrapped_in_attempt = function 108 | Attempt _ -> true 109 | _ -> false 110 111let rec check_expr env = function 112 | Alt { loc; p; q } -> 113 let p_first = First_set.compute env p in 114 let q_first = First_set.compute env q in 115 let errs = 116 if is_wrapped_in_attempt p || is_wrapped_in_attempt q then 117 [] 118 else if not (First_set.definitely_disjoint p_first q_first) then 119 [Ambiguous_alt { loc }] 120 else 121 [] 122 in 123 errs @ check_expr env p @ check_expr env q 124 | Bind { p; _ } -> check_expr env p 125 | Map { p; _ } -> check_expr env p 126 | Apply { pf; px; _ } -> check_expr env pf @ check_expr env px 127 | SeqLeft { p; q; _ } -> check_expr env p @ check_expr env q 128 | SeqRight { p; q; _ } -> check_expr env p @ check_expr env q 129 | Attempt { p; _ } -> check_expr env p 130 | Lookahead { p; _ } -> check_expr env p 131 | NotFollowedBy { p; _ } -> check_expr env p 132 | Many { p; _ } -> check_expr env p 133 | Some_ { p; _ } -> check_expr env p 134 | Optional { p; _ } -> check_expr env p 135 | Option { p; _ } -> check_expr env p 136 | SepBy { p; sep; _ } -> check_expr env p @ check_expr env sep 137 | SepBy1 { p; sep; _ } -> check_expr env p @ check_expr env sep 138 | EndBy { p; sep; _ } -> check_expr env p @ check_expr env sep 139 | EndBy1 { p; sep; _ } -> check_expr env p @ check_expr env sep 140 | ManyTill { p; end_; _ } -> check_expr env p @ check_expr env end_ 141 | Count { p; _ } -> check_expr env p 142 | Between { open_; close; p; _ } -> check_expr env open_ @ check_expr env close @ check_expr env p 143 | ChainL { p; op; _ } -> check_expr env p @ check_expr env op 144 | ChainL1 { p; op; _ } -> check_expr env p @ check_expr env op 145 | ChainR { p; op; _ } -> check_expr env p @ check_expr env op 146 | ChainR1 { p; op; _ } -> check_expr env p @ check_expr env op 147 | Choice { ps; _ } -> List.concat_map (check_expr env) ps 148 | Label { p; _ } -> check_expr env p 149 | Pure _ | Fail _ | Satisfy _ | Any _ | Eof _ | Token _ | Tokens _ 150 | OneOf _ | NoneOf _ | Cut _ | Fix _ | Var _ -> [] 151 152let error_loc = function 153 | Left_recursion { loc; _ } -> loc 154 | Ambiguous_alt { loc } -> loc 155 156let format_error = function 157 | Left_recursion { name; path; _ } -> 158 Printf.sprintf "left recursion detected: %s via %s" 159 name (String.concat " -> " path) 160 | Ambiguous_alt _ -> 161 "ambiguous alternation: branches may have overlapping first sets, wrap one in 'attempt'"