// Search for a sequence of conversions from expression `e` to a destination // category c and type t. Expr& standard_conversion(Context& cxt, Expr& e, Type& t) { // No conversions are required if the types are the same. if (is_equivalent(e.type(), t)) return e; // Try a categorical conversion. Expr& c1 = convert_category(cxt, e, t); if (is_equivalent(c1.type(), t)) return c1; // Try a value conversion. Expr& c2 = convert_value(cxt, c1, t); if (is_equivalent(c2.type(), t)) return c2; // Try a qualification adjustment. Expr& c3 = convert_qualifier(cxt, c2, t); if (is_equivalent(c3.type(), t)) return c3; error(cxt, "cannot convert '{}' (type '{}') to '{}'", e, e.type(), t); throw Type_error(); }
void test_types() { Type& t1 = *new Void_type(); Type& t2 = *new Boolean_type(); Type& t3 = *new Integer_type(); Type& t4 = *new Float_type(); assert(is_equivalent(t1, t1)); assert(is_equivalent(t2, t2)); assert(is_equivalent(t3, t3)); assert(is_equivalent(t4, t4)); assert(!is_equivalent(t1, t2)); }
bool action::is_compatible(action const & a) const { if (is_equivalent(a)) return true; auto k1 = kind(); auto k2 = a.kind(); return is_compatible_core(k1, k2) || is_compatible_core(k2, k1); }
// Perform the usual arithmetic conversions on `e1` and `e2`. This // tries to find a common type for `e1` and `e2` and convert both // expressions to that type. // // NOTE: In C++ lvalue-to-rvalue conversions are required on a // per-expression basis, independently of converting to a common // type. Also, non-user-defined types are unqualified prior to // analysis. It would be easier if we found an absolute common // type and then instantiated a declaration suitable for overload // resolution. Maybe. // // TODO: Handle conversions for character types (or promote to a // corresponding integer type?). // // TODO: How does bool work with this set of conversions? Promote // bool to int? // // TODO: Can we unify this with the common type required by the // conditional expression? Note that the arithmetic version converts // to values, and the conditional expression can retain references. Expr_pair arithmetic_conversions(Context& cxt, Expr& e1, Expr& e2) { // If the types are the same, no conversions are applied. if (is_equivalent(e1.type(), e2.type())) return {e1, e2}; // If either operand has floating point type, convert to the type // with the greatest precision. // // TODO: Why isn't convert_to_common_float symmetric? This seems like // an issue. if (has_floating_point_type(e1)) return convert_to_common_float(cxt, e2, e1); if (has_floating_point_type(e2)) return convert_to_common_float(cxt, e1, e2); // If both operands have integer type, the following rules apply. if (has_integer_type(e1) && has_integer_type(e2)) return convert_to_common_int(cxt, e1, e2); // TODO: No conversion from e1 to e2. error(cxt, "no usual arithmetic conversion of '{}' and '{}'", e1, e2); throw Type_error(); }
inline bool is_equivalent(List<T> const& a, List<T> const& b) { auto cmp = [](T const& x, T const& y) { return is_equivalent(x, y); }; return std::equal(a.begin(), a.end(), b.begin(), b.end(), cmp); }
// Try to find a standard conversion sequence from a source // expression `e` and a destination type `t`. // // FIXME: Should `t` be an object type? That is we should perform // conversions iff we can declare an object of type T? Expr& standard_conversion(Expr& e, Type& t) { Expr& c1 = convert_category(e, t); if (is_equivalent(c1.type(), t)) return c1; Expr& c2 = convert_value(c1, t); if (is_equivalent(c2.type(), t)) return c2; Expr& c3 = convert_qualifier(c2, t); if (is_equivalent(c3.type(), t)) return c3; // FIXME: Emit better diagnostics. throw std::runtime_error("conversion error"); }
// FIXME: Handle precedence. Prop const* simplify(Or const* p) { Prop const* p1 = simplify(p->left()); Prop const* p2 = simplify(p->right()); // idempotence: p or p <=> p if (is_equivalent(p1, p2)) return p1; // absorption: p or (p and q) <=> p // // Because 'and' and 'or' are comutative, we need to // check a bunch of different combinations. if (And const* p3 = as<And>(p2)) { // p or (p and q) if (is_equivalent(p1, p3->left())) return p1; // p or (q and p) if (is_equivalent(p1, p3->right())) return p1; } else if (And const* p3 = as<And>(p1)) { // (p and q) or p if (is_equivalent(p2, p3->left())) return p2; // (q and p) or p if (is_equivalent(p2, p3->right())) return p2; } // contraction: p or (p or q) <=> p or q if (Or const* p3 = as<Or>(p2)) { // p or (p or q) if (is_equivalent(p1, p3->left())) return p3; // p or (q or p) if (is_equivalent(p1, p3->right())) return p3; } else if (Or const* p3 = as<Or>(p1)) { // (p or q) or p if (is_equivalent(p2, p3->left())) return p3; // (q or p) or p if (is_equivalent(p2, p3->right())) return p3; } return new Or(p1, p2); }
// Returns true if a subsumes c. // // TODO: How do I know when I've exhuasted all opportunities. bool subsumes(Context& cxt, Cons const& a, Cons const& c) { // Check the easy cases before setting up a proof. if (is_equivalent(a, c)) return true; if (is_memoized(cxt, a, c)) return true; // Alas... no quick check. We have to prove the implication. Proof p(cxt); Sequent& s = p.front(); s.antecedents().insert(a); s.consequents().insert(c); std::cout << "INIT: " << s << '\n'; // NOTE: I wonder if the current load implementation is // too aggressive when expanding concepts. // Initially load consquents. load_consequents(p); // Continue manipulating the proof state until we know that // the implication is valid or not. int n = 1; Validation v = valid_proof; do { // Load a round of antecedents. load_antecedents(p); std::cout << "STEP " << n << ": " << p.front() << '\n'; // Having done that, determine if the proof is valid (or not). // In either case, we can stop. v = check_proof(p); std::cout << "VALID? " << v << '\n'; if (v == valid_proof) return true; if (v == invalid_proof) return false; // Otherwise, select a term in each goal to expand. expand_proof(p); ++n; // TODO: Actually diagnose implementation limits. Note that the // real limiting factor is going to be the goal size, not // the step count. if (p.size() > 32) throw Limitation_error("exceeded proof subgoal limit"); if (n > 1024) throw Limitation_error("exceeded proof step limit"); } while (v == incomplete_proof); return false; }
Prop const* simplify(And const* p) { Prop const* p1 = simplify(p->left()); Prop const* p2 = simplify(p->right()); // idempotence: p and p <=> p if (is_equivalent(p1, p2)) return p1; // absorption: p and (p or q) <=> p if (Or const* p3 = as<Or>(p2)) { // p and (p or q) if (is_equivalent(p1, p3->left())) return p1; // p and (q or p) if (is_equivalent(p1, p3->right())) return p1; } else if (Or const* p3 = as<Or>(p1)) { // (p or q) and p if (is_equivalent(p2, p3->left())) return p2; // (q or p) and p if (is_equivalent(p2, p3->right())) return p2; } // contraction: p and (p and q) <=> p and q if (And const* p3 = as<And>(p2)) { // p and (p and q) if (is_equivalent(p1, p3->left())) return p3; // p and (q and p) if (is_equivalent(p1, p3->right())) return p3; } else if (And const* p3 = as<And>(p1)) { // (p and q) and p if (is_equivalent(p2, p3->left())) return p3; // (q and p) and p if (is_equivalent(p2, p3->right())) return p3; } return new And(p1, p2); }
// An expression is admissible for a disjinction of assumptions if // support is found both the left and right operand. inline Expr* admit_disjunction_expr(Context& cxt, Disjunction_cons& c, Expr& e) { if (Expr* e1 = admit_expression(cxt, c.left(), e)) { if (Expr* e2 = admit_expression(cxt, c.right(), e)) { if (!is_equivalent(e1->type(), e2->type())) throw Translation_error(cxt, "multiple types deduced for '{}'", e); return e1; } } return nullptr; }
// Perform the usual arithmetic conversions on `e1` and `e2`. This // tries to find a common type for `e1` and `e2` and convert both // expressions to that type. // // NOTE: In C++ lvalue-to-rvalue conversions are required on a // per-expression basis, independently of converting to a common // type. Also, non-user-defined types are unqualified prior to // analysis. It would be easier if we found an absolute common // type and then instantiated a declaration suitable for overload // resolution. Maybe. // // TODO: Handle conversions for character types (or promote to a // corresponding integer type?). // // TODO: How does bool work with this set of conversions? Promote // bool to int? // // TODO: Can we unify this with the common type required by the // conditional expression? Note that the arithmetic version converts // to values, and the conditional expression can retain references. Expr_pair arithmetic_conversion(Expr& e1, Expr& e2) { // If the types are the same, no conversions are applied. if (is_equivalent(e1.type(), e2.type())) return {e1, e2}; // If either operand has floating point type, convert to the type // with the greatest precision. if (has_floating_point_type(e1)) return convert_to_common_float(e2, e1); if (has_floating_point_type(e2)) return convert_to_common_float(e1, e2); // If both oerands have integer type, the following rules apply. if (has_integer_type(e1) && has_integer_type(e2)) return convert_to_common_int(e1, e2); // TODO: No conversion from e1 to e2. throw std::runtime_error("incompatible types"); }
Expr* admit_usage_conv(Context& cxt, Conversion_cons& c, Expr& e, Type& t) { struct fn { Context& cxt; Conversion_cons& c; Expr* operator()(Expr& e) { banjo_unhandled_case(e); } Expr* operator()(Binary_expr& e) { return admit_binary_conv(cxt, c, e); } }; // An expression of a different kind prove admissibility. Expr& e2 = c.expression(); if (typeid(e2) != typeid(e)) return nullptr; // If the expression's type is not equivalent to t, this constraint // does not prove admissibility. if (!is_equivalent(c.type(), t)) return nullptr; return apply(e, fn{cxt, c}); }
bool operator()(Float_type const& a) { return is_equivalent(a, cast<Float_type>(b)); }
bool operator()(Void_type const& a) { return is_equivalent(a, cast<Void_type>(b)); }
bool operator()(Synthetic_type const& a) { return is_equivalent(a, cast<Synthetic_type>(b)); }
bool operator()(Typename_type const& a) { return is_equivalent(a, cast<Typename_type>(b)); }
bool operator()(Enum_type const& a) { return is_equivalent(a, cast<Enum_type>(b)); }
bool operator()(Type const& a) { return is_equivalent(a, b); }
// Returns true when type t1 differs from type t2. inline bool is_different(Type const& t1, Type const& t2) { return !is_equivalent(t1, t2); }
bool operator()(Boolean_type const& a) { return is_equivalent(a, cast<Boolean_type>(b)); }
bool operator()(T const& a, T const& b) const { return is_equivalent(a, b); }
bool operator()(Integer_type const& a) { return is_equivalent(a, cast<Integer_type>(b)); }
vm_obj level_eqv(vm_obj const & o1, vm_obj const & o2) { return mk_vm_bool(is_equivalent(to_level(o1), to_level(o2))); }
bool operator()(Declauto_type const& a) { return is_equivalent(a, cast<Declauto_type>(b)); }
bool operator()(List<T> const& a, List<T> const& b) const { return is_equivalent(a, b); }
bool operator()(Reference_type const& a) { return is_equivalent(a, cast<Reference_type>(b)); }
bool operator()(Class_type const& a) { return is_equivalent(a, cast<Class_type>(b)); }
bool operator()(Union_type const& a) { return is_equivalent(a, cast<Union_type>(b)); }
bool operator()(T const* a, T const* b) const { return is_equivalent(*a, *b); }
// Two tuple types are similar only when they are equivalent. // // TODO: There is probably a valid generalization of this. Maybe // two tuple types are similar if all their elements are similar? bool is_similar(Tuple_type const& a, Tuple_type const& b) { return is_equivalent(a, b); }