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