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