type outcome = Solved of Pattern.Subst.t list | Stuck let forced_label name = match Label.of_string name with Ok l -> l | Error _ -> assert false let value_l = forced_label "value" let left_l = forced_label "left" let right_l = forced_label "right" let result_l = forced_label "result" let lower_l = forced_label "lower" let upper_l = forced_label "upper" let names = [ "True"; "Integer"; "Natural"; "Rational"; "String"; "Equal"; "Plus"; "Times"; "LessThan"; "LessThanEqual"; "GreaterThan"; "GreaterEqual"; "Between"; ] let is_builtin name = List.mem name names let fail = Solved [] let yes subst = Solved [ subst ] (* Numeric view: integers and rationals compute together in Q, and results come back through Term.rational, which restores the minimal shape. An integer-valued result is an Integer. *) let to_q = function | Pattern.Integer z -> Some (Q.of_bigint z) | Pattern.Rational q -> Some q | _ -> None let of_q q = match Term.rational ~num:(Q.num q) ~den:(Q.den q) with | Ok term -> Pattern.of_term term | Error `Zero_denominator -> assert false let arg subst bindings label = Option.map (Pattern.Subst.resolve subst) (Label.Map.find_opt label bindings) (* Domain membership. Unbound arguments are Stuck, never enumerated: membership is not generation. The inclusions are the literal ones of decision 12. *) let domain_check subst bindings check = match arg subst bindings value_l with | None -> Stuck | Some pattern -> if Pattern.is_ground pattern then if check pattern then yes subst else fail else Stuck let comparison subst bindings compare = match (arg subst bindings left_l, arg subst bindings right_l) with | Some left, Some right -> ( match (to_q left, to_q right) with | Some l, Some r -> if compare (Q.compare l r) 0 then yes subst else fail | _ -> if Pattern.is_ground left && Pattern.is_ground right then fail else Stuck) | _ -> Stuck (* Plus and Times solve backwards: any two ground numeric arguments determine the third by unification. Times through zero cannot determine the missing factor and stays Stuck rather than guess. *) let arithmetic subst bindings ~forward ~from_result_left ~from_result_right = let left = arg subst bindings left_l and right = arg subst bindings right_l and result = Label.Map.find_opt result_l bindings in let unify_result value = match result with | Some pattern -> ( match Pattern.Subst.unify subst pattern value with | Some subst -> yes subst | None -> fail) | None -> yes subst in let solve_side binding solved = match binding with | Some pattern -> ( match solved with | Some value -> ( match Pattern.Subst.unify subst pattern value with | Some subst -> yes subst | None -> fail) | None -> Stuck) | None -> Stuck in match (Option.bind left to_q, Option.bind right to_q) with | Some l, Some r -> unify_result (of_q (forward l r)) | Some l, None -> ( match Option.bind result (fun p -> to_q (Pattern.Subst.resolve subst p)) with | Some res -> solve_side right (Option.map of_q (from_result_left res l)) | None -> Stuck) | None, Some r -> ( match Option.bind result (fun p -> to_q (Pattern.Subst.resolve subst p)) with | Some res -> solve_side left (Option.map of_q (from_result_right res r)) | None -> Stuck) | None, None -> Stuck let between subst bindings = let value = Label.Map.find_opt value_l bindings and lower = arg subst bindings lower_l and upper = arg subst bindings upper_l in match (lower, upper) with | Some (Pattern.Integer lo), Some (Pattern.Integer hi) -> ( match Option.map (Pattern.Subst.resolve subst) value with | Some (Pattern.Integer v) -> if Z.geq v lo && Z.leq v hi then yes subst else fail | Some (Pattern.Var _) | None -> (* Bounded generation: the one place enumeration is admitted, because the range is finite by construction. *) let rec enumerate v acc = if Z.gt v hi then List.rev acc else let acc = match value with | Some pattern -> ( match Pattern.Subst.unify subst pattern (Pattern.Integer v) with | Some subst -> subst :: acc | None -> acc) | None -> subst :: acc in enumerate (Z.succ v) acc in Solved (enumerate lo []) | Some _ -> fail) | Some p, Some q when Pattern.is_ground p && Pattern.is_ground q -> fail | _ -> Stuck let run subst name bindings = match name with | "True" -> Some (yes subst) | "Integer" -> Some (domain_check subst bindings (function | Pattern.Integer _ -> true | _ -> false)) | "Natural" -> Some (domain_check subst bindings (function | Pattern.Integer z -> Z.geq z Z.zero | _ -> false)) | "Rational" -> Some (domain_check subst bindings (function | Pattern.Integer _ | Pattern.Rational _ -> true | _ -> false)) | "String" -> Some (domain_check subst bindings (function | Pattern.String _ -> true | _ -> false)) | "Equal" -> ( match (Label.Map.find_opt left_l bindings, Label.Map.find_opt right_l bindings) with | Some left, Some right -> Some (match Pattern.Subst.unify subst left right with | Some subst -> yes subst | None -> fail) | _ -> Some Stuck) | "Plus" -> Some (arithmetic subst bindings ~forward:Q.add ~from_result_left:(fun res l -> Some (Q.sub res l)) ~from_result_right:(fun res r -> Some (Q.sub res r))) | "Times" -> Some (arithmetic subst bindings ~forward:Q.mul ~from_result_left:(fun res l -> if Q.equal l Q.zero then None else Some (Q.div res l)) ~from_result_right:(fun res r -> if Q.equal r Q.zero then None else Some (Q.div res r))) | "LessThan" -> Some (comparison subst bindings ( < )) | "LessThanEqual" -> Some (comparison subst bindings ( <= )) | "GreaterThan" -> Some (comparison subst bindings ( > )) | "GreaterEqual" -> Some (comparison subst bindings ( >= )) | "Between" -> Some (between subst bindings) | _ -> None