#include #include #include #include #include #include #include #include #include #include #include "ast/arith_decl_plugin.h" #include "ast/array_decl_plugin.h" #include "ast/expr_abstract.h" #include "ast/ast_util.h" #include "cmd_context/cmd_context.h" #include "cmd_context/tptp_frontend.h" #include "solver/solver.h" #include "util/error_codes.h" #include "util/rational.h" #include "util/timeout.h" #include "util/z3_exception.h" bool g_display_statistics = false; bool g_display_model = false; static void on_timeout() { std::cout << "% SZS status Timeout\n"; std::cout.flush(); _Exit(0); } namespace { enum class token_kind { eof_tok, id, str, lparen, rparen, lbrack, rbrack, comma, dot, colon, and_tok, or_tok, not_tok, forall_tok, exists_tok, type_forall_tok, // !> type_exists_tok, // ?* equal_tok, neq_tok, iff_tok, implies_tok, implied_tok, xor_tok, nor_tok, nand_tok, gt_tok, star_tok, slash_tok, minus_tok, at_tok, lambda_tok }; struct parse_error : public std::exception { std::string m_msg; parse_error(std::string const& msg): m_msg(msg) {} char const* what() const noexcept override { return m_msg.c_str(); } }; class scoped_regular_stream { cmd_context& m_ctx; std::string m_prev; public: scoped_regular_stream(cmd_context& ctx, std::ostream& out): m_ctx(ctx), m_prev(ctx.get_regular_stream_name()) { m_ctx.set_regular_stream(out); } ~scoped_regular_stream() { m_ctx.set_regular_stream(m_prev.c_str()); } }; struct token { token_kind kind = token_kind::eof_tok; std::string text; unsigned line = 1; unsigned col = 1; }; class lexer { std::string const& m_input; size_t m_pos = 0; unsigned m_line = 1; unsigned m_col = 1; bool eof() const { return m_pos >= m_input.size(); } char peek(unsigned k = 0) const { return m_pos + k < m_input.size() ? m_input[m_pos + k] : '\0'; } char get() { char c = peek(); if (!eof()) { ++m_pos; if (c == '\n') { ++m_line; m_col = 1; } else { ++m_col; } } return c; } static bool is_symbol_start(char c) { return std::isalnum(static_cast(c)) || c == '$' || c == '_'; } static bool is_id_char(char c) { return std::isalnum(static_cast(c)) || c == '$' || c == '_' || c == '\'' || c == '-'; } void skip_ws_comments() { while (!eof()) { if (std::isspace(static_cast(peek()))) { get(); continue; } if (peek() == '%') { while (!eof() && get() != '\n') {} continue; } if (peek() == '/' && peek(1) == '*') { get(); get(); while (!eof()) { if (peek() == '*' && peek(1) == '/') { get(); get(); break; } get(); } continue; } break; } } public: lexer(std::string const& input): m_input(input) {} token next() { skip_ws_comments(); token t; t.line = m_line; t.col = m_col; if (eof()) { t.kind = token_kind::eof_tok; return t; } if (peek() == '\'' || peek() == '"') { char q = get(); t.kind = token_kind::str; while (!eof()) { char c = get(); if (c == '\\' && !eof()) { t.text.push_back(c); t.text.push_back(get()); continue; } if (c == q) return t; t.text.push_back(c); } throw parse_error("unterminated string literal"); } if (peek() == '<' && peek(1) == '=' && peek(2) == '>') { get(); get(); get(); t.kind = token_kind::iff_tok; t.text = "<=>"; return t; } if (peek() == '<' && peek(1) == '~' && peek(2) == '>') { get(); get(); get(); t.kind = token_kind::xor_tok; t.text = "<~>"; return t; } if (peek() == '=' && peek(1) == '>') { get(); get(); t.kind = token_kind::implies_tok; t.text = "=>"; return t; } if (peek() == '<' && peek(1) == '=') { get(); get(); t.kind = token_kind::implied_tok; t.text = "<="; return t; } if (peek() == '~' && peek(1) == '|') { get(); get(); t.kind = token_kind::nor_tok; t.text = "~|"; return t; } if (peek() == '~' && peek(1) == '&') { get(); get(); t.kind = token_kind::nand_tok; t.text = "~&"; return t; } if (peek() == '!' && peek(1) == '=') { get(); get(); t.kind = token_kind::neq_tok; t.text = "!="; return t; } char c = get(); switch (c) { case '(': t.kind = token_kind::lparen; return t; case ')': t.kind = token_kind::rparen; return t; case '[': t.kind = token_kind::lbrack; return t; case ']': t.kind = token_kind::rbrack; return t; case ',': t.kind = token_kind::comma; return t; case '.': t.kind = token_kind::dot; return t; case ':': t.kind = token_kind::colon; return t; case '&': t.kind = token_kind::and_tok; return t; case '|': t.kind = token_kind::or_tok; return t; case '~': t.kind = token_kind::not_tok; return t; case '!': if (peek() == '>') { get(); t.kind = token_kind::type_forall_tok; return t; } t.kind = token_kind::forall_tok; return t; case '?': if (peek() == '*') { get(); t.kind = token_kind::type_exists_tok; return t; } t.kind = token_kind::exists_tok; return t; case '=': t.kind = token_kind::equal_tok; return t; case '>': t.kind = token_kind::gt_tok; return t; case '*': t.kind = token_kind::star_tok; return t; case '/': t.kind = token_kind::slash_tok; return t; case '-': t.kind = token_kind::minus_tok; return t; case '@': t.kind = token_kind::at_tok; return t; case '^': t.kind = token_kind::lambda_tok; return t; default: break; } if (is_symbol_start(c)) { t.kind = token_kind::id; t.text.push_back(c); while (!eof() && is_id_char(peek())) t.text.push_back(get()); return t; } std::ostringstream out; out << "unexpected character '" << c << "' at " << t.line << ":" << t.col; throw parse_error(out.str()); } }; struct parsed_type { std::vector domain; sort* range = nullptr; parsed_type(sort* s): range(s) {} parsed_type(std::vector const& d, sort* r): domain(d), range(r) {} }; class tptp_parser { cmd_context& m_cmd; ast_manager& m; arith_util m_arith; array_util m_array; sort* m_univ; bool m_has_conjecture = false; bool m_last_name_quoted = false; std::unordered_map m_sorts; sort_ref_vector m_pinned_sorts; // prevents cached sorts from being freed std::unordered_map m_decls; func_decl_ref_vector m_pinned_decls; // prevents cached func_decls from being freed expr_ref_vector m_pinned_exprs; // prevents bound variable apps from being freed std::unordered_map, sort*>> m_typed_decls; std::vector> m_bound; bool m_in_at_arg = false; // true when parsing inside @ argument (lambda body stops consuming @) struct implicit_var_scope { std::unordered_map vars; ptr_vector order; }; implicit_var_scope* m_implicit_scope = nullptr; std::unordered_set m_seen_files; // Table-driven operator dispatch using op_builder = std::function; struct op_entry { bool is_infix; unsigned precedence; // only meaningful for infix; higher = tighter binding bool right_assoc; op_builder builder; }; std::unordered_map m_ops; // Infix precedence levels: static constexpr unsigned PREC_IFF = 1; // <=> <~> static constexpr unsigned PREC_IMPLIES = 2; // => <= static constexpr unsigned PREC_OR = 3; // | ~| static constexpr unsigned PREC_AND = 4; // & ~& static constexpr unsigned PREC_EQ = 5; // = != std::string m_input; std::unique_ptr m_lex; token m_curr; // Helper: check arity for arithmetic operators void check_arith_arity(expr_ref_vector const& args, unsigned expected, char const* name) { if (args.size() != expected) { std::ostringstream out; out << "'" << name << "' expects arity " << expected; throw parse_error(out.str()); } } // Helper: coerce two arithmetic args to same sort (promote int to real if needed) std::pair coerce_arith2(expr_ref_vector const& args) { expr_ref a(args[0], m), b(args[1], m); if (m_arith.is_real(a) || m_arith.is_real(b)) { if (m_arith.is_int(a)) a = expr_ref(m_arith.mk_to_real(a), m); if (m_arith.is_int(b)) b = expr_ref(m_arith.mk_to_real(b), m); } return { a, b }; } // Helper: quotient dispatch (integer division for int/int, real division otherwise) expr_ref mk_quotient(expr_ref_vector const& args) { expr_ref a(args[0], m), b(args[1], m); if (m_arith.is_int(a) && m_arith.is_int(b)) return expr_ref(m_arith.mk_idiv(a, b), m); if (m_arith.is_int(a)) a = expr_ref(m_arith.mk_to_real(a), m); if (m_arith.is_int(b)) b = expr_ref(m_arith.mk_to_real(b), m); return expr_ref(m_arith.mk_div(a, b), m); } // Map infix token to operator name (returns nullptr if not an infix op token) char const* token_to_op_name() const { switch (m_curr.kind) { case token_kind::iff_tok: return "<=>"; case token_kind::xor_tok: return "<~>"; case token_kind::implies_tok: return "=>"; case token_kind::implied_tok: return "<="; case token_kind::or_tok: return "|"; case token_kind::nor_tok: return "~|"; case token_kind::and_tok: return "&"; case token_kind::nand_tok: return "~&"; case token_kind::equal_tok: return "="; case token_kind::neq_tok: return "!="; default: return nullptr; } } static std::string to_lower(std::string s) { for (char& c : s) c = static_cast(std::tolower(static_cast(c))); return s; } static bool is_var_name(std::string const& s) { if (s.empty()) return false; unsigned char c = static_cast(s[0]); return std::isupper(c) || s[0] == '_'; } std::string loc() const { std::ostringstream out; out << m_curr.line << ":" << m_curr.col; return out.str(); } void next() { m_curr = m_lex->next(); } bool is(token_kind k) const { return m_curr.kind == k; } bool accept(token_kind k) { if (is(k)) { next(); return true; } return false; } void expect(token_kind k, char const* msg) { if (!accept(k)) { std::ostringstream out; out << "expected " << msg << " at " << loc(); throw parse_error(out.str()); } } std::string parse_name() { if (is(token_kind::id) || is(token_kind::str)) { m_last_name_quoted = is(token_kind::str); std::string r = m_curr.text; next(); return r; } std::ostringstream out; out << "expected identifier at " << loc(); throw parse_error(out.str()); } sort* get_sort(std::string const& n) { if (n == "$i") return m_univ; if (n == "$o") return m.mk_bool_sort(); if (n == "$int") return m_arith.mk_int(); if (n == "$rat" || n == "$real") return m_arith.mk_real(); auto it = m_sorts.find(n); if (it != m_sorts.end()) return it->second; sort* s = m.mk_uninterpreted_sort(symbol(n)); m_sorts.emplace(n, s); m_pinned_sorts.push_back(s); return s; } // For higher-order types like ($i > $o), create an uninterpreted sort // Function type A > B is represented as Array(A, B). // Multi-argument A * B > C is represented as Array(A, Array(B, C)) (curried). sort* get_ho_sort(std::vector const& domain, sort* range) { sort* s = range; for (int i = (int)domain.size() - 1; i >= 0; --i) s = m_array.mk_array_sort(domain[i], s); return s; } static bool is_ttype(sort* s) { return s->get_name() == symbol("$tType"); } static bool is_nonempty_digit_string(std::string const& s) { if (s.empty()) return false; for (char c : s) { if (!std::isdigit(static_cast(c))) return false; } return true; } expr_ref parse_numeral_from_name(std::string const& n) { SASSERT(is_nonempty_digit_string(n)); rational num(n.c_str()); if (accept(token_kind::dot)) { std::string frac = parse_name(); if (!is_nonempty_digit_string(frac)) throw parse_error("fractional part of decimal literal must be a sequence of digits"); rational den(1); for (unsigned i = 0; i < frac.size(); ++i) { den *= rational(10); } rational frac_num(frac.c_str()); return expr_ref(m_arith.mk_numeral(num + frac_num / den, false), m); } if (accept(token_kind::slash_tok)) { std::string d = parse_name(); if (!is_nonempty_digit_string(d)) throw parse_error("denominator of rational literal must be a sequence of digits"); rational den(d.c_str()); if (den.is_zero()) throw parse_error("denominator of rational literal cannot be zero"); return expr_ref(m_arith.mk_numeral(num / den, false), m); } return expr_ref(m_arith.mk_numeral(num, true), m); } static std::string mk_decl_key(std::string const& name, unsigned arity, char tag) { return std::to_string(name.size()) + ":" + name + "\x1f" + std::to_string(arity) + "\x1f" + tag; } static std::string mk_typed_key(std::string const& name, unsigned arity) { return mk_decl_key(name, arity, 't'); } func_decl* mk_decl(std::string const& name, unsigned arity, bool pred) { auto itt = m_typed_decls.find(mk_typed_key(name, arity)); if (itt != m_typed_decls.end()) { std::string typed_decl_key = mk_decl_key(name, arity, 'd'); auto itd = m_decls.find(typed_decl_key); if (itd != m_decls.end()) return itd->second; auto const& sig = itt->second; func_decl* f = m.mk_func_decl(symbol(name), sig.first.size(), sig.first.data(), sig.second); m_pinned_decls.push_back(f); m_decls.emplace(typed_decl_key, f); return f; } std::string key = mk_decl_key(name, arity, pred ? 'p' : 'f'); auto itd = m_decls.find(key); if (itd != m_decls.end()) return itd->second; std::vector dom(arity, m_univ); func_decl* f = m.mk_func_decl(symbol(name), arity, dom.data(), pred ? m.mk_bool_sort() : m_univ); m_pinned_decls.push_back(f); m_decls.emplace(key, f); return f; } // When a symbol is used with 0 args but has a typed decl with arity > 0, // create a 0-arity constant with the function type sort (for THF function-as-value). func_decl* mk_decl_or_ho_const(std::string const& name, unsigned arity, bool pred) { if (arity == 0) { // Check if there's a typed decl at any arity > 0 for this name for (unsigned try_arity = 1; try_arity <= 30; ++try_arity) { auto itt = m_typed_decls.find(mk_typed_key(name, try_arity)); if (itt != m_typed_decls.end()) { auto const& sig = itt->second; sort* ho = get_ho_sort(sig.first, sig.second); std::string dkey = mk_decl_key(name, 0, 'h'); auto itd = m_decls.find(dkey); if (itd != m_decls.end()) return itd->second; func_decl* f = m.mk_func_decl(symbol(name), 0, static_cast(nullptr), ho); m_pinned_decls.push_back(f); m_decls.emplace(dkey, f); return f; } } } return mk_decl(name, arity, pred); } // Coerce an expression to a target sort using boxing/unboxing functions expr_ref coerce_arg(expr_ref const& e, sort* target) { sort* actual = e->get_sort(); if (actual == target) return e; // Create a boxing function from actual sort to target sort std::string box_name = std::string("$box_") + actual->get_name().str() + "_to_" + target->get_name().str(); std::string key = mk_decl_key(box_name, 1, 'f'); auto it = m_decls.find(key); func_decl* f; if (it != m_decls.end()) { f = it->second; } else { f = m.mk_func_decl(symbol(box_name), 1, &actual, target); m_pinned_decls.push_back(f); m_decls.emplace(key, f); } return expr_ref(m.mk_app(f, e.get()), m); } // Coerce arguments of a function application to match declared sorts void coerce_args(func_decl* f, expr_ref_vector& args) { for (unsigned i = 0; i < args.size() && i < f->get_arity(); ++i) { sort* expected = f->get_domain(i); sort* actual = args.get(i)->get_sort(); if (expected != actual) { args[i] = coerce_arg(expr_ref(args.get(i), m), expected); } } } // Coerce result to expected sort if needed expr_ref coerce_result(expr_ref const& e, sort* expected) { if (!expected || e->get_sort() == expected) return e; return coerce_arg(e, expected); } bool find_bound(std::string const& n, expr_ref& e) const { for (auto it = m_bound.rbegin(); it != m_bound.rend(); ++it) { auto jt = it->find(n); if (jt != it->end()) { e = jt->second; return true; } } return false; } bool is_bound_var(app* a) const { std::string name = a->get_decl()->get_name().str(); for (auto it = m_bound.rbegin(); it != m_bound.rend(); ++it) { auto jt = it->find(name); if (jt != it->end() && jt->second == a) return true; } return false; } bool should_create_implicit_var(std::string const& n) const { return is_var_name(n) && m_implicit_scope; } app* get_or_create_implicit_var(std::string const& n) { if (!m_implicit_scope) throw parse_error("unexpected parser state: missing implicit variable scope"); auto it = m_implicit_scope->vars.find(n); if (it != m_implicit_scope->vars.end()) return it->second; app* c = m.mk_const(symbol(n), m_univ); m_pinned_exprs.push_back(c); m_implicit_scope->vars.emplace(n, c); m_implicit_scope->order.push_back(c); return c; } class scoped_implicit_vars { tptp_parser& m_p; implicit_var_scope* m_prev_scope; public: scoped_implicit_vars(tptp_parser& p, implicit_var_scope& scope): m_p(p), m_prev_scope(p.m_implicit_scope) { m_p.m_implicit_scope = &scope; } scoped_implicit_vars(scoped_implicit_vars const&) = delete; scoped_implicit_vars& operator=(scoped_implicit_vars const&) = delete; scoped_implicit_vars(scoped_implicit_vars&&) = delete; scoped_implicit_vars& operator=(scoped_implicit_vars&&) = delete; ~scoped_implicit_vars() { m_p.m_implicit_scope = m_prev_scope; } }; expr_ref mk_quantifier(bool is_forall, ptr_vector const& bound, expr_ref const& body) { SASSERT(body); if (bound.empty()) return body; return is_forall ? ::mk_forall(m, bound.size(), bound.data(), body.get()) : ::mk_exists(m, bound.size(), bound.data(), body.get()); } // $is_rat(x) ≡ exists a:Int, b:Int. b != 0 && x = a/b expr_ref mk_is_rat(expr_ref const& x) { sort* int_sort = m_arith.mk_int(); app* a = m.mk_fresh_const("a", int_sort); app* b = m.mk_fresh_const("b", int_sort); expr_ref ar(m_arith.mk_to_real(a), m); expr_ref br(m_arith.mk_to_real(b), m); expr_ref xr(x); if (m_arith.is_int(x)) xr = expr_ref(m_arith.mk_to_real(x), m); expr_ref b_ne_zero(m.mk_not(m.mk_eq(b, m_arith.mk_int(0))), m); expr_ref x_eq_div(m.mk_eq(xr, m_arith.mk_div(ar, br)), m); expr_ref body(m.mk_and(b_ne_zero, x_eq_div), m); ptr_vector bound; bound.push_back(a); bound.push_back(b); return expr_ref(::mk_exists(m, bound.size(), bound.data(), body.get()), m); } parsed_type parse_type_atom() { if (accept(token_kind::lparen)) { std::vector prod = parse_type_product_raw(); if (accept(token_kind::gt_tok)) { // Full function type inside parens: (A * B > C) or (A > B > C) parsed_type rhs = parse_type_expr(); std::vector full_domain = prod; if (!rhs.domain.empty()) { // Nested higher-order: (A > B > C) → flatten full_domain.insert(full_domain.end(), rhs.domain.begin(), rhs.domain.end()); } expect(token_kind::rparen, "')'"); // Return with domain/range preserved for proper flattening return parsed_type(full_domain, rhs.range); } expect(token_kind::rparen, "')'"); if (prod.size() == 1) return parsed_type(prod[0]); // Parenthesized product: (A * B) — used as domain in outer context return parsed_type(prod, nullptr); } std::string n = parse_name(); // Handle parameterized type constructors: fun(A, B), product_prod(A, B), etc. if (accept(token_kind::lparen)) { // Consume type arguments — for monomorphization, we ignore them // and return the base sort (or m_univ if the constructor result is $tType) if (!accept(token_kind::rparen)) { do { parse_type_expr(); } while (accept(token_kind::comma)); expect(token_kind::rparen, "')'"); } // Return m_univ as the monomorphized result of any type constructor application return parsed_type(m_univ); } return parsed_type(get_sort(n)); } std::vector parse_type_product_raw() { parsed_type first = parse_type_atom(); if (!first.domain.empty() && first.range == nullptr) { // Already a parenthesized product from nested parens std::vector args = first.domain; while (accept(token_kind::star_tok)) { parsed_type t = parse_type_atom(); if (!t.domain.empty()) { args.insert(args.end(), t.domain.begin(), t.domain.end()); } else { args.push_back(t.range); } } return args; } if (!first.domain.empty()) { // Function type as first element of product — use ho_sort sort* ho = get_ho_sort(first.domain, first.range); std::vector args; args.push_back(ho); while (accept(token_kind::star_tok)) { parsed_type t = parse_type_atom(); if (!t.domain.empty() && t.range != nullptr) { args.push_back(get_ho_sort(t.domain, t.range)); } else if (!t.domain.empty()) { args.insert(args.end(), t.domain.begin(), t.domain.end()); } else { args.push_back(t.range); } } return args; } std::vector args; args.push_back(first.range); while (accept(token_kind::star_tok)) { parsed_type t = parse_type_atom(); if (!t.domain.empty() && t.range != nullptr) { args.push_back(get_ho_sort(t.domain, t.range)); } else if (!t.domain.empty()) { args.insert(args.end(), t.domain.begin(), t.domain.end()); } else { args.push_back(t.range); } } return args; } parsed_type parse_type_product() { parsed_type first = parse_type_atom(); // If atom returned a function type and no '*' follows, return it directly if (!first.domain.empty() && first.range != nullptr && !is(token_kind::star_tok)) { return first; } // Build product vector std::vector args; if (!first.domain.empty() && first.range != nullptr) { // Function type used as element in a product args.push_back(get_ho_sort(first.domain, first.range)); } else if (!first.domain.empty() && first.range == nullptr) { // Parenthesized product: flatten args = first.domain; } else { args.push_back(first.range); } while (accept(token_kind::star_tok)) { parsed_type t = parse_type_atom(); if (!t.domain.empty() && t.range != nullptr) { args.push_back(get_ho_sort(t.domain, t.range)); } else if (!t.domain.empty()) { args.insert(args.end(), t.domain.begin(), t.domain.end()); } else { args.push_back(t.range); } } return parsed_type(args, nullptr); } parsed_type parse_type_expr() { // Handle type quantification at the expression level for proper domain/range preservation if (is(token_kind::type_forall_tok) || is(token_kind::type_exists_tok)) { next(); expect(token_kind::lbrack, "'['"); std::vector type_params; if (!accept(token_kind::rbrack)) { do { std::string tv = parse_name(); if (accept(token_kind::colon)) parse_type_expr(); // consume $tType annotation m_sorts.insert_or_assign(tv, m_univ); type_params.push_back(m_univ); } while (accept(token_kind::comma)); expect(token_kind::rbrack, "']'"); } expect(token_kind::colon, "':'"); parsed_type inner = parse_type_expr(); // Prepend type params to domain if (!type_params.empty()) { std::vector full_domain = type_params; full_domain.insert(full_domain.end(), inner.domain.begin(), inner.domain.end()); return parsed_type(full_domain, inner.range); } return inner; } parsed_type prod = parse_type_product(); if (accept(token_kind::gt_tok)) { parsed_type rhs = parse_type_expr(); // prod is either a product (domain non-empty, range==nullptr) or a single sort (domain empty) std::vector domain; if (!prod.domain.empty() && prod.range == nullptr) { domain = prod.domain; } else if (!prod.domain.empty() && prod.range != nullptr) { // A function type as domain element — wrap it domain.push_back(get_ho_sort(prod.domain, prod.range)); } else { domain.push_back(prod.range); } if (!rhs.domain.empty()) { // Higher-order result type: A > (B > C) flattened to (A, B) > C domain.insert(domain.end(), rhs.domain.begin(), rhs.domain.end()); return parsed_type(domain, rhs.range); } return parsed_type(domain, rhs.range); } // No '>' follows — must be a single type or a function type from parens if (!prod.domain.empty() && prod.range != nullptr) { // Function type from parenthesized expression return prod; } if (!prod.domain.empty() && prod.range == nullptr) { if (prod.domain.size() != 1) throw parse_error("type product must be followed by '>'"); return parsed_type(prod.domain[0]); } return parsed_type(prod.range); } void skip_annotations_until_rparen() { int depth = 0; while (!is(token_kind::eof_tok)) { if (accept(token_kind::lparen) || accept(token_kind::lbrack)) { ++depth; continue; } if (is(token_kind::rparen) || is(token_kind::rbrack)) { if (depth == 0) return; --depth; next(); continue; } next(); } } void skip_balanced(token_kind open_k, token_kind close_k) { int depth = 1; while (depth > 0 && !is(token_kind::eof_tok)) { if (accept(open_k)) ++depth; else if (accept(close_k)) --depth; else next(); } } expr_ref parse_term(); expr_ref parse_term_primary() { if (accept(token_kind::lparen)) { expr_ref e = parse_formula(); expect(token_kind::rparen, "')'"); return e; } if (accept(token_kind::lambda_tok)) { return parse_lambda_expr(); } if (accept(token_kind::minus_tok)) { expr_ref e = parse_term_primary(); if (!m_arith.is_int_real(e)) throw parse_error("unary '-' expects arithmetic term"); return expr_ref(m_arith.mk_uminus(e), m); } std::string n = parse_name(); if (n == "$true") return expr_ref(m.mk_true(), m); if (n == "$false") return expr_ref(m.mk_false(), m); if (is_nonempty_digit_string(n)) { return parse_numeral_from_name(n); } expr_ref b(m); if (!m_last_name_quoted && is_var_name(n) && find_bound(n, b)) return b; if (!m_last_name_quoted && should_create_implicit_var(n)) return expr_ref(get_or_create_implicit_var(n), m); expr_ref_vector args(m); // $ite needs special parsing: first arg is formula, rest are formulas (branches can be equalities) if (n == "$ite") { expect(token_kind::lparen, "'('"); args.push_back(parse_formula()); expect(token_kind::comma, "','"); args.push_back(parse_formula()); expect(token_kind::comma, "','"); args.push_back(parse_formula()); expect(token_kind::rparen, "')'"); } else if (accept(token_kind::lparen)) { if (!accept(token_kind::rparen)) { do { args.push_back(parse_term()); } while (accept(token_kind::comma)); expect(token_kind::rparen, "')'"); } } // Table-driven prefix operator dispatch auto op_it = m_ops.find(n); if (op_it != m_ops.end() && !op_it->second.is_infix) { return op_it->second.builder(args); } func_decl* f = mk_decl_or_ho_const(n, args.size(), false); if (!args.empty()) coerce_args(f, args); return expr_ref(args.empty() ? m.mk_const(f) : m.mk_app(f, args.size(), args.data()), m); } expr_ref parse_formula(); expr_ref apply_at(expr_ref e) { if (!is(token_kind::at_tok)) return e; // @ corresponds to array select (function application) while (accept(token_kind::at_tok)) { expr_ref arg = parse_at_arg(); sort* e_sort = e->get_sort(); if (!m_array.is_array(e_sort)) { std::ostringstream out; out << "application operator (@) requires array/function type on left-hand side, got sort " << e_sort->get_name(); throw parse_error(out.str()); } e = expr_ref(m_array.mk_select(e, arg), m); } return e; } // Parse an argument to @ — can be a term, a formula (negation, quantifier, parens with connectives), or a lambda expr_ref parse_at_arg() { if (accept(token_kind::not_tok)) { expr_ref e = parse_at_arg(); return expr_ref(m.mk_not(e), m); } if (accept(token_kind::lambda_tok)) { return parse_lambda_expr(); } if (accept(token_kind::lparen)) { expr_ref e = parse_formula(); expect(token_kind::rparen, "')'"); // Do NOT call apply_at here — outer apply_at owns the remaining @ tokens return e; } if (is(token_kind::forall_tok) || is(token_kind::exists_tok)) { bool is_forall = is(token_kind::forall_tok); next(); expect(token_kind::lbrack, "'['"); ptr_vector vars; std::unordered_map scope; if (!accept(token_kind::rbrack)) { do { std::string v = parse_name(); sort* s = m_univ; if (accept(token_kind::colon)) { parsed_type t = parse_type_expr(); if (!t.domain.empty()) s = get_ho_sort(t.domain, t.range); else s = t.range; } app* c = m.mk_const(symbol(v), s); m_pinned_exprs.push_back(c); vars.push_back(c); scope.emplace(v, c); } while (accept(token_kind::comma)); expect(token_kind::rbrack, "']'"); } expect(token_kind::colon, "':'"); m_bound.push_back(scope); // Quantifier body in @-arg should NOT consume @ — those belong to enclosing application bool save_in_at_arg = m_in_at_arg; m_in_at_arg = true; expr_ref body = parse_formula(); m_in_at_arg = save_in_at_arg; m_bound.pop_back(); return mk_quantifier(is_forall, vars, body); } // Simple term (name with optional function args) — no @ consumption here return parse_term_primary(); } // Build an arithmetic expression from a TPTP function name and arguments // Coerce two expressions to have the same sort for equality. // If sorts already match, returns lhs unchanged. Otherwise coerces the // 0-arity constant side to match the other's sort. If that's not possible, // coerces both to m_univ. expr_ref coerce_eq(expr_ref lhs, expr_ref& rhs) { if (lhs->get_sort() == rhs->get_sort()) return lhs; // Try coercing one side (0-arity constants can be reinterpreted) if (is_app(lhs) && to_app(lhs)->get_num_args() == 0 && lhs->get_sort() != rhs->get_sort()) { func_decl* fd = m.mk_func_decl(to_app(lhs)->get_decl()->get_name(), 0, static_cast(nullptr), rhs->get_sort()); m_pinned_decls.push_back(fd); return expr_ref(m.mk_const(fd), m); } if (is_app(rhs) && to_app(rhs)->get_num_args() == 0 && lhs->get_sort() != rhs->get_sort()) { func_decl* fd = m.mk_func_decl(to_app(rhs)->get_decl()->get_name(), 0, static_cast(nullptr), lhs->get_sort()); m_pinned_decls.push_back(fd); rhs = m.mk_const(fd); return lhs; } // Last resort: coerce both to m_univ if (is_app(lhs) && to_app(lhs)->get_num_args() == 0) { func_decl* fd = m.mk_func_decl(to_app(lhs)->get_decl()->get_name(), 0, static_cast(nullptr), m_univ); m_pinned_decls.push_back(fd); lhs = m.mk_const(fd); } if (is_app(rhs) && to_app(rhs)->get_num_args() == 0) { func_decl* fd = m.mk_func_decl(to_app(rhs)->get_decl()->get_name(), 0, static_cast(nullptr), m_univ); m_pinned_decls.push_back(fd); rhs = m.mk_const(fd); } return lhs; } expr_ref parse_atomic_formula() { if (accept(token_kind::lparen)) { // Parentheses create a new scope for @ consumption bool save_in_at_arg = m_in_at_arg; m_in_at_arg = false; expr_ref e = parse_formula(); expect(token_kind::rparen, "')'"); m_in_at_arg = save_in_at_arg; return e; } // Handle negative numerals in formula position: -2 = $uminus(2) if (accept(token_kind::minus_tok)) { expr_ref t = parse_term(); return expr_ref(m_arith.mk_uminus(t), m); } std::string n = parse_name(); if (n == "$true") return expr_ref(m.mk_true(), m); if (n == "$false") return expr_ref(m.mk_false(), m); if (is_nonempty_digit_string(n)) { return parse_numeral_from_name(n); } expr_ref_vector args(m); // $ite needs special parsing: first arg is formula, rest are formulas (branches can be equalities) if (n == "$ite") { expect(token_kind::lparen, "'('"); args.push_back(parse_formula()); expect(token_kind::comma, "','"); args.push_back(parse_formula()); expect(token_kind::comma, "','"); args.push_back(parse_formula()); expect(token_kind::rparen, "')'"); } else if (accept(token_kind::lparen)) { if (!accept(token_kind::rparen)) { do { args.push_back(parse_term()); } while (accept(token_kind::comma)); expect(token_kind::rparen, "')'"); } } // Table-driven prefix operator dispatch auto op_it = m_ops.find(n); if (op_it != m_ops.end() && !op_it->second.is_infix) { return op_it->second.builder(args); } expr_ref lhs(m); bool has_lhs = false; if (args.empty()) { expr_ref b(m); if (!m_last_name_quoted && is_var_name(n) && find_bound(n, b)) { lhs = b; has_lhs = true; } else if (!m_last_name_quoted && should_create_implicit_var(n)) { lhs = expr_ref(get_or_create_implicit_var(n), m); has_lhs = true; } } if (has_lhs) return lhs; auto typed = m_typed_decls.find(mk_typed_key(n, args.size())); if (typed != m_typed_decls.end()) { func_decl* f = args.empty() ? mk_decl_or_ho_const(n, 0, false) : mk_decl(n, args.size(), false); if (!args.empty()) coerce_args(f, args); return expr_ref(args.empty() ? m.mk_const(f) : m.mk_app(f, args.size(), args.data()), m); } func_decl* pred = mk_decl_or_ho_const(n, args.size(), true); if (!args.empty()) coerce_args(pred, args); return expr_ref(args.empty() ? m.mk_const(pred) : m.mk_app(pred, args.size(), args.data()), m); } // Parse THF lambda expression: ^ [X: T, ...] : body // Uses Z3's native lambda construct, which produces array terms. expr_ref parse_lambda_expr() { expect(token_kind::lbrack, "'['"); ptr_vector vars; std::unordered_map scope; if (!accept(token_kind::rbrack)) { do { std::string v = parse_name(); sort* s = m_univ; if (accept(token_kind::colon)) { parsed_type t = parse_type_expr(); if (!t.domain.empty()) { s = get_ho_sort(t.domain, t.range); } else if (t.range) { s = t.range; } } app* c = m.mk_const(symbol(v), s); m_pinned_exprs.push_back(c); vars.push_back(c); scope.emplace(v, c); } while (accept(token_kind::comma)); expect(token_kind::rbrack, "']'"); } expect(token_kind::colon, "':'"); m_bound.push_back(scope); // Lambda body does NOT consume @ — @ belongs to the enclosing application bool save_in_at_arg = m_in_at_arg; m_in_at_arg = true; expr_ref body = parse_formula(); m_in_at_arg = save_in_at_arg; m_bound.pop_back(); if (vars.empty()) return body; // Create nested single-variable lambdas (curried) to match our curried array encoding. // ^[X:A, Y:B] : body becomes ^[X:A] : (^[Y:B] : body) with sort Array(A, Array(B, body_sort)) expr_ref result = body; for (int i = (int)vars.size() - 1; i >= 0; --i) { expr_ref abs_body(m); expr_abstract(m, 0, 1, (expr* const*)&vars[i], result, abs_body); sort* s = vars[i]->get_sort(); symbol nm = vars[i]->get_decl()->get_name(); result = expr_ref(m.mk_lambda(1, &s, &nm, abs_body), m); } return result; } expr_ref parse_unary_formula() { if (accept(token_kind::not_tok)) { expr_ref e = parse_unary_formula(); return expr_ref(m.mk_not(e), m); } if (accept(token_kind::lambda_tok)) { // THF lambda: ^ [X: T, ...] : body // Approximate as a fresh constant (first-order approximation) return parse_lambda_expr(); } if (is(token_kind::forall_tok) || is(token_kind::exists_tok)) { bool is_forall = is(token_kind::forall_tok); next(); expect(token_kind::lbrack, "'['"); ptr_vector vars; std::unordered_map scope; if (!accept(token_kind::rbrack)) { do { std::string v = parse_name(); sort* s = m_univ; if (accept(token_kind::colon)) { parsed_type t = parse_type_expr(); if (!t.domain.empty()) { // Higher-order variable type — use uninterpreted sort approximation s = get_ho_sort(t.domain, t.range); } else { s = t.range; } } // Monomorphize: $tType-sorted variables become U-sorted // and register them as sorts for subsequent type references if (is_ttype(s)) { s = m_univ; m_sorts.insert_or_assign(v, m_univ); } app* c = m.mk_const(symbol(v), s); m_pinned_exprs.push_back(c); vars.push_back(c); scope.emplace(v, c); } while (accept(token_kind::comma)); expect(token_kind::rbrack, "']'"); } expect(token_kind::colon, "':'"); m_bound.push_back(scope); expr_ref body = parse_formula(); m_bound.pop_back(); return mk_quantifier(is_forall, vars, body); } // Type quantification in formula context: !>[A: $tType, ...] : body // Erase type variables and parse body as formula if (is(token_kind::type_forall_tok) || is(token_kind::type_exists_tok)) { next(); expect(token_kind::lbrack, "'['"); if (!accept(token_kind::rbrack)) { do { std::string tv = parse_name(); if (accept(token_kind::colon)) parse_type_expr(); // consume $tType annotation m_sorts.insert_or_assign(tv, m_univ); } while (accept(token_kind::comma)); expect(token_kind::rbrack, "']'"); } expect(token_kind::colon, "':'"); return parse_formula(); } return parse_atomic_formula(); } expr_ref parse_expr(unsigned min_prec, bool consume_at = true) { expr_ref e = parse_unary_formula(); for (;;) { // Handle @ (function application) with highest precedence // But NOT when we're inside a lambda body that's an @ argument if (consume_at && !m_in_at_arg && is(token_kind::at_tok)) { next(); expr_ref arg = parse_at_arg(); sort* e_sort = e->get_sort(); if (!m_array.is_array(e_sort)) { std::ostringstream out; out << "application operator (@) requires array/function type on left-hand side, got sort " << e_sort->get_name(); throw parse_error(out.str()); } e = expr_ref(m_array.mk_select(e, arg), m); continue; } char const* op_name = token_to_op_name(); if (!op_name) break; auto it = m_ops.find(op_name); if (it == m_ops.end() || !it->second.is_infix) break; if (it->second.precedence < min_prec) break; next(); // consume the operator token unsigned next_prec = it->second.right_assoc ? it->second.precedence : it->second.precedence + 1; expr_ref rhs = parse_expr(next_prec, consume_at); expr_ref_vector args(m); args.push_back(e); args.push_back(rhs); e = it->second.builder(args); } return e; } void parse_type_decl_formula() { unsigned lparen_count = 0; while (accept(token_kind::lparen)) ++lparen_count; std::string name = parse_name(); expect(token_kind::colon, "':'"); parsed_type t = parse_type_expr(); while (lparen_count-- > 0) expect(token_kind::rparen, "')'"); if (t.domain.empty() && is_ttype(t.range)) { // Sort declaration: monomorphize to m_univ m_sorts.insert_or_assign(name, m_univ); return; } // Monomorphize: replace $tType in domain/range with m_univ for (auto& s : t.domain) { if (is_ttype(s)) s = m_univ; } if (t.range && is_ttype(t.range)) t.range = m_univ; m_typed_decls.insert_or_assign(mk_typed_key(name, t.domain.size()), std::make_pair(t.domain, t.range)); } static bool file_exists(std::string const& f) { std::ifstream in(f); return !in.fail(); } static bool is_absolute_path(std::string const& name) { return !name.empty() && (name[0] == '/' || (name.size() >= 2 && std::isalpha(static_cast(name[0])) && name[1] == ':')); } std::string dirname(std::string const& f) const { size_t idx = f.find_last_of("/\\"); return idx == std::string::npos ? "." : f.substr(0, idx); } static std::string normalize_path(std::string path) { #ifdef _WIN32 for (auto& c : path) if (c == '/') c = '\\'; #endif return path; } std::string resolve_include(std::string const& curr_file, std::string const& name) const { if (is_absolute_path(name)) return normalize_path(name); // Try relative to current file's directory std::string local = normalize_path(dirname(curr_file) + "/" + name); if (file_exists(local)) return local; // Try TPTP environment variable (standard TPTP convention) char const* root = std::getenv("TPTP"); if (root) { std::string env = normalize_path(std::string(root) + "/" + name); if (file_exists(env)) return env; } // Try relative to current working directory (common when running from TPTP root) std::string cwd_relative = normalize_path(name); if (file_exists(cwd_relative)) return cwd_relative; return local; } void parse_include(std::string const& curr_file) { expect(token_kind::lparen, "'('"); std::string file = parse_name(); if (accept(token_kind::comma)) { if (accept(token_kind::lbrack)) { skip_balanced(token_kind::lbrack, token_kind::rbrack); } else { skip_annotations_until_rparen(); } } expect(token_kind::rparen, "')'"); expect(token_kind::dot, "'.'"); parse_file(resolve_include(curr_file, file)); } void parse_annotated() { expect(token_kind::lparen, "'('"); parse_name(); expect(token_kind::comma, "','"); std::string role = to_lower(parse_name()); expect(token_kind::comma, "','"); if (role == "type") { parse_type_decl_formula(); } else { implicit_var_scope implicit_scope; scoped_implicit_vars scoped(*this, implicit_scope); expr_ref f = parse_formula(); if (!implicit_scope.order.empty()) { f = mk_quantifier(true, implicit_scope.order, f); } if (role == "conjecture") { m_has_conjecture = true; if (m.is_bool(f)) f = m.mk_not(f); } // Only assert Bool-sorted formulas; non-Bool results from // incomplete higher-order approximation are silently skipped. if (m.is_bool(f)) m_cmd.assert_expr(f); } if (accept(token_kind::comma)) { skip_annotations_until_rparen(); } expect(token_kind::rparen, "')'"); expect(token_kind::dot, "'.'"); } void parse_toplevel(std::string const& current_file) { while (!is(token_kind::eof_tok)) { std::string kw = to_lower(parse_name()); if (kw == "include") { parse_include(current_file); } else if (kw == "fof" || kw == "cnf" || kw == "tff" || kw == "thf") { parse_annotated(); } else { std::ostringstream out; out << "unsupported TPTP unit '" << kw << "' at " << loc(); throw parse_error(out.str()); } } } public: tptp_parser(cmd_context& cmd): m_cmd(cmd), m(m_cmd.m()), m_arith(m), m_array(m), m_pinned_sorts(m), m_pinned_decls(m), m_pinned_exprs(m), m_univ(m.mk_uninterpreted_sort(symbol("U"))) { m_pinned_sorts.push_back(m_univ); sort* tType = m.mk_uninterpreted_sort(symbol("$tType")); m_pinned_sorts.push_back(tType); m_sorts.emplace("$tType", tType); m_sorts.emplace("$i", m_univ); m_sorts.emplace("$o", m.mk_bool_sort()); m_sorts.emplace("$int", m_arith.mk_int()); m_sorts.emplace("$rat", m_arith.mk_real()); m_sorts.emplace("$real", m_arith.mk_real()); init_op_table(); } void init_op_table() { // Prefix arithmetic predicates (is_infix=false, precedence=0) m_ops["$less"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$less"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_lt(a, b), m); }}; m_ops["$lesseq"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$lesseq"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_le(a, b), m); }}; m_ops["$greater"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$greater"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_gt(a, b), m); }}; m_ops["$greatereq"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$greatereq"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_ge(a, b), m); }}; m_ops["$uminus"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$uminus"); return expr_ref(m_arith.mk_uminus(args[0]), m); }}; m_ops["$sum"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$sum"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_add(a, b), m); }}; m_ops["$plus"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$plus"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_add(a, b), m); }}; m_ops["$difference"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$difference"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_sub(a, b), m); }}; m_ops["$product"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$product"); auto [a, b] = coerce_arith2(args); return expr_ref(m_arith.mk_mul(a, b), m); }}; m_ops["$quotient"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$quotient"); return mk_quotient(args); }}; m_ops["$quotient_e"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$quotient_e"); return mk_quotient(args); }}; m_ops["$quotient_t"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$quotient_t"); return mk_quotient(args); }}; m_ops["$quotient_f"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$quotient_f"); return mk_quotient(args); }}; m_ops["$remainder_e"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$remainder_e"); return expr_ref(m_arith.mk_mod(args[0], args[1]), m); }}; m_ops["$remainder_t"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$remainder_t"); return expr_ref(m_arith.mk_mod(args[0], args[1]), m); }}; m_ops["$remainder_f"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 2, "$remainder_f"); return expr_ref(m_arith.mk_mod(args[0], args[1]), m); }}; m_ops["$floor"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$floor"); expr_ref a(args[0], m); if (m_arith.is_int(a)) return a; return expr_ref(m_arith.mk_to_int(a), m); }}; m_ops["$ceiling"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$ceiling"); expr_ref a(args[0], m); if (m_arith.is_int(a)) return a; // ceiling(x) = -floor(-x) return expr_ref(m_arith.mk_uminus(m_arith.mk_to_int(m_arith.mk_uminus(a))), m); }}; m_ops["$truncate"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$truncate"); expr_ref a(args[0], m); if (m_arith.is_int(a)) return a; // truncate(x) = if x >= 0 then floor(x) else ceiling(x) expr_ref zero(m_arith.mk_real(0), m); expr_ref fl(m_arith.mk_to_int(a), m); expr_ref neg_fl(m_arith.mk_uminus(m_arith.mk_to_int(m_arith.mk_uminus(a))), m); return expr_ref(m.mk_ite(m_arith.mk_ge(a, zero), fl, neg_fl), m); }}; m_ops["$round"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$round"); expr_ref a(args[0], m); if (m_arith.is_int(a)) return a; // round to nearest even expr_ref i(m_arith.mk_to_int(a), m); expr_ref half(m_arith.mk_add(m_arith.mk_to_real(i), m_arith.mk_numeral(rational(1, 2), false)), m); expr_ref i1(m_arith.mk_add(i, m_arith.mk_int(1)), m); expr_ref is_even(m.mk_eq(m_arith.mk_mod(i, m_arith.mk_int(2)), m_arith.mk_int(0)), m); return expr_ref(m.mk_ite(m_arith.mk_gt(a, half), i1, m.mk_ite(m.mk_eq(a, half), m.mk_ite(is_even, i, i1), i)), m); }}; m_ops["$to_int"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$to_int"); expr_ref a(args[0], m); if (m_arith.is_int(a)) return a; return expr_ref(m_arith.mk_to_int(a), m); }}; m_ops["$to_real"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$to_real"); expr_ref a(args[0], m); if (m_arith.is_real(a)) return a; return expr_ref(m_arith.mk_to_real(a), m); }}; m_ops["$to_rat"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$to_rat"); expr_ref a(args[0], m); if (m_arith.is_real(a)) return a; return expr_ref(m_arith.mk_to_real(a), m); }}; m_ops["$is_int"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$is_int"); return expr_ref(m_arith.mk_is_int(args[0]), m); }}; m_ops["$is_rat"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$is_rat"); expr_ref a(args[0], m); return mk_is_rat(a); }}; m_ops["$distinct"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { if (args.size() == 2) return expr_ref(m.mk_not(m.mk_eq(args[0], args[1])), m); return expr_ref(m.mk_distinct(args.size(), args.data()), m); }}; m_ops["$ite"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 3, "$ite"); expr_ref cond(args[0], m), t(args[1], m), f(args[2], m); if (!m.is_bool(cond)) throw parse_error("$ite expects Bool condition as first argument"); return expr_ref(m.mk_ite(cond, t, f), m); }}; m_ops["$abs"] = { false, 0, false, [&](expr_ref_vector const& args) -> expr_ref { check_arith_arity(args, 1, "$abs"); expr_ref a(args[0], m); if (!m_arith.is_int_real(a)) throw parse_error("$abs expects arithmetic argument"); expr_ref zero(m_arith.is_int(a) ? m_arith.mk_int(0) : m_arith.mk_numeral(rational(0), false), m); return expr_ref(m.mk_ite(m_arith.mk_ge(a, zero), a, expr_ref(m_arith.mk_uminus(a), m)), m); }}; m_ops["$true"] = { false, 0, false, [&](expr_ref_vector const&) -> expr_ref { return expr_ref(m.mk_true(), m); }}; m_ops["$false"] = { false, 0, false, [&](expr_ref_vector const&) -> expr_ref { return expr_ref(m.mk_false(), m); }}; // Infix logical operators (token-based, matched by token_to_op_name) m_ops["<=>"] = { true, PREC_IFF, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_iff(args[0], args[1]), m); }}; m_ops["<~>"] = { true, PREC_IFF, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_not(m.mk_iff(args[0], args[1])), m); }}; m_ops["=>"] = { true, PREC_IMPLIES, true, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_implies(args[0], args[1]), m); }}; m_ops["<="] = { true, PREC_IMPLIES, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_implies(args[1], args[0]), m); }}; m_ops["|"] = { true, PREC_OR, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_or(args[0], args[1]), m); }}; m_ops["~|"] = { true, PREC_OR, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_not(m.mk_or(args[0], args[1])), m); }}; m_ops["&"] = { true, PREC_AND, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_and(args[0], args[1]), m); }}; m_ops["~&"] = { true, PREC_AND, false, [&](expr_ref_vector const& args) -> expr_ref { return expr_ref(m.mk_not(m.mk_and(args[0], args[1])), m); }}; m_ops["="] = { true, PREC_EQ, false, [&](expr_ref_vector const& args) -> expr_ref { expr_ref lhs(args[0], m); expr_ref rhs(args[1], m); lhs = coerce_eq(lhs, rhs); return expr_ref(m.mk_eq(lhs, rhs), m); }}; m_ops["!="] = { true, PREC_EQ, false, [&](expr_ref_vector const& args) -> expr_ref { expr_ref lhs(args[0], m); expr_ref rhs(args[1], m); lhs = coerce_eq(lhs, rhs); return expr_ref(m.mk_not(m.mk_eq(lhs, rhs)), m); }}; } void parse_input(std::istream& in, std::string const& current_file) { // Save parser state so that included files don't clobber the caller's lexer. std::string saved_input = std::move(m_input); std::unique_ptr saved_lex = std::move(m_lex); token saved_curr = m_curr; std::ostringstream buf; buf << in.rdbuf(); m_input = buf.str(); m_lex = std::make_unique(m_input); next(); parse_toplevel(current_file); // Restore caller's parser state. m_input = std::move(saved_input); m_lex = std::move(saved_lex); m_curr = saved_curr; } void parse_file(std::string const& filename) { if (!m_seen_files.insert(filename).second) return; std::ifstream in(filename); if (in.fail()) { std::ostringstream out; out << "failed to open file '" << filename << "'"; throw parse_error(out.str()); } parse_input(in, filename); } void parse_stream(std::istream& in) { parse_input(in, "."); } bool has_conjecture() const { return m_has_conjecture; } }; expr_ref tptp_parser::parse_term() { expr_ref e = parse_term_primary(); if (!is(token_kind::at_tok)) return e; // @ corresponds to array select (function application) while (accept(token_kind::at_tok)) { expr_ref arg = parse_at_arg(); sort* e_sort = e->get_sort(); if (!m_array.is_array(e_sort)) { std::ostringstream out; out << "application operator (@) requires array/function type on left-hand side, got sort " << e_sort->get_name(); throw parse_error(out.str()); } e = expr_ref(m_array.mk_select(e, arg), m); } return e; } expr_ref tptp_parser::parse_formula() { return parse_expr(PREC_IFF); } } static unsigned read_tptp_stream(std::istream& in, char const* current_file) { register_on_timeout_proc(on_timeout); try { cmd_context ctx; ctx.set_solver_factory(mk_smt_strategic_solver_factory()); tptp_parser p(ctx); p.parse_input(in, current_file ? current_file : "."); // Suppress default check-sat output; TPTP frontend reports SZS status explicitly. std::ostringstream sink; scoped_regular_stream scoped_stream(ctx, sink); ctx.check_sat(0, nullptr); switch (ctx.cs_state()) { case cmd_context::css_unsat: if (p.has_conjecture()) std::cout << "% SZS status Theorem\n"; else std::cout << "% SZS status Unsatisfiable\n"; break; case cmd_context::css_sat: if (p.has_conjecture()) std::cout << "% SZS status CounterSatisfiable\n"; else std::cout << "% SZS status Satisfiable\n"; if (g_display_model) { model_ref mdl; if (ctx.is_model_available(mdl)) ctx.display_model(mdl); } break; case cmd_context::css_unknown: std::cout << "% SZS status GaveUp\n"; { std::string reason = ctx.reason_unknown(); if (!reason.empty()) std::cout << "% SZS reason " << reason << "\n"; } break; default: break; } if (g_display_statistics) { ctx.set_regular_stream("stdout"); ctx.display_statistics(); } return 0; } catch (parse_error const& ex) { std::cerr << "TPTP parse error: " << ex.what() << "\n"; return ERR_PARSER; } catch (z3_error const& ex) { if (ex.error_code() == ERR_TIMEOUT) { std::cout << "% SZS status Timeout\n"; return 0; } std::cerr << "TPTP frontend error: " << ex.what() << "\n"; return ERR_INTERNAL_FATAL; } catch (z3_exception const& ex) { std::cerr << "TPTP frontend error: " << ex.what() << "\n"; return ERR_INTERNAL_FATAL; } } unsigned read_tptp(char const* file_name) { if (!file_name) return read_tptp_stream(std::cin, "."); std::ifstream in(file_name); if (in.fail()) { std::cerr << "TPTP parse error: failed to open file '" << file_name << "'\n"; return ERR_PARSER; } return read_tptp_stream(in, file_name); } unsigned read_tptp_string(char const* input) { std::istringstream in(input ? input : ""); return read_tptp_stream(in, ""); }