/* * richard-atp-engine: A saturation-based automated theorem prover for first-order logic. * Copyright (C) 2026 Richard Qin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "atp/frontend/tptp_parser.h" #include "atp/core/clause.h" #include "atp/core/literal.h" #include "atp/core/term_bank.h" #include "atp/core/types.h" #include #include #include #include #include #include #include namespace atp { FormulaRole parseRole(std::string_view role_str) { if (role_str == "axiom") return FormulaRole::kAxiom; if (role_str == "hypothesis") return FormulaRole::kHypothesis; if (role_str == "definition") return FormulaRole::kDefinition; if (role_str == "assumption") return FormulaRole::kAssumption; if (role_str == "lemma") return FormulaRole::kLemma; if (role_str == "theorem") return FormulaRole::kTheorem; if (role_str == "conjecture") return FormulaRole::kConjecture; if (role_str == "negated_conjecture") return FormulaRole::kNegatedConjecture; if (role_str == "plain") return FormulaRole::kPlain; return FormulaRole::kPlain; } enum class TokenType { kWord, kLParen, kRParen, kOr, kNot, kDot, kComma, kEOF, kUnkown }; struct Token { TokenType type; std::string_view value; size_t line; size_t column; }; class TptpLexer { public: explicit TptpLexer(std::string_view input) : source_(input) {} Token next_token() { skip_whitespace_and_comments(); if (pos_ >= source_.length()) { return {.type = TokenType::kEOF, .value = "", .line = line_, .column = column_}; } size_t start_pos = pos_; [[maybe_unused]] size_t start_col = column_; char c = source_[pos_]; if (std::isalpha(static_cast(c)) || c == '_') { while ( pos_ < source_.size() && (std::isalnum(static_cast(source_[pos_])) || source_[pos_] == '_')) { advance_char(); } return {.type = TokenType::kWord, .value = source_.substr(start_pos, pos_ - start_pos), .line = line_, .column = column_}; } advance_char(); switch (c) { case '(': return {.type = TokenType::kLParen, .value = "(", .line = line_, .column = column_}; case ')': return {.type = TokenType::kRParen, .value = ")", .line = line_, .column = column_}; case ',': return {.type = TokenType::kComma, .value = ",", .line = line_, .column = column_}; case '.': return {.type = TokenType::kDot, .value = ".", .line = line_, .column = column_}; case '~': return {.type = TokenType::kNot, .value = "~", .line = line_, .column = column_}; case '|': return {.type = TokenType::kOr, .value = "|", .line = line_, .column = column_}; default: return {.type = TokenType::kUnkown, .value = source_.substr(start_pos, 1), .line = line_, .column = column_}; } } private: std::string_view source_; size_t pos_ = 0; size_t line_ = 1; size_t column_ = 1; void advance_char() { if (pos_ < source_.size()) { if (source_[pos_] == '\n') { line_++; column_ = 1; } else { column_++; } pos_++; } } void skip_whitespace_and_comments() { while (pos_ < source_.size()) { char c = source_[pos_]; if (std::isspace(static_cast(c))) { advance_char(); } else if (c == '%') { while (pos_ < source_.size() && source_[pos_] != '\n') { advance_char(); } } else { break; } } } }; class TptpParser { public: explicit TptpParser(std::string_view input, TermBank& bank, SymbolTable& symbols) : lexer_(input), bank_(bank), symbols_(symbols) { advance(); } void parseProblem(ParsedProblem& problem) { while (current_token_.type != TokenType::kEOF) { if (current_token_.type == TokenType::kWord && current_token_.value == "cnf") { advance(); expect(TokenType::kLParen); AnnotatedFormula af; af.is_cnf = true; af.name = std::string(current_token_.value); expect(TokenType::kWord); expect(TokenType::kComma); af.role = parseRole(current_token_.value); expect(TokenType::kWord); expect(TokenType::kComma); Clause clause; clause.literals.push_back(parseLiteral()); while (current_token_.type == TokenType::kOr) { advance(); clause.literals.push_back(parseLiteral()); } af.clauses.push_back(std::move(clause)); expect(TokenType::kRParen); expect(TokenType::kDot); problem.formulas.push_back(std::move(af)); variable_cache_.clear(); } else if (current_token_.value == "fof") { throw std::runtime_error("FOF parsing not implemented. Line " + std::to_string(current_token_.line)); } else { throw std::runtime_error("Unknown declaration: '" + std::string(current_token_.value) + "'"); } } } private: TptpLexer lexer_; TermBank& bank_; SymbolTable& symbols_; Token current_token_; std::unordered_map variable_cache_; void advance() { current_token_ = lexer_.next_token(); } void expect(TokenType type) { if (current_token_.type != type) { throw std::runtime_error("Parse error at line " + std::to_string(current_token_.line) + ", col " + std::to_string(current_token_.column) + ": unexpected token '" + std::string(current_token_.value) + "'"); } advance(); } TermId parseTerm() { if (current_token_.type != TokenType::kWord) { throw std::runtime_error("Expected variable or function name at line " + std::to_string(current_token_.line) + ", col " + std::to_string(current_token_.column) + ": unexpected token '" + std::string(current_token_.value) + "'"); } std::string_view name = current_token_.value; bool is_variable = std::isupper(static_cast(name[0])); advance(); if (is_variable) { // Check cache first — same variable name in one clause → same TermId auto it = variable_cache_.find(name); if (it != variable_cache_.end()) { return it->second; } SymbolId sym = symbols_.intern(name, SymbolKind::kVariable); TermId var_id = bank_.makeVar(sym); variable_cache_[name] = var_id; return var_id; } else { // Check if this is a function/predicate (has args) or a constant (no parens) if (current_token_.type == TokenType::kLParen) { advance(); // consume '(' std::vector args; if (current_token_.type != TokenType::kRParen) { args.push_back(parseTerm()); while (current_token_.type == TokenType::kComma) { advance(); args.push_back(parseTerm()); } } expect(TokenType::kRParen); SymbolId sym = symbols_.intern(name, SymbolKind::kFunction, static_cast(args.size())); return bank_.makeTerm(sym, args); } else { // Constant: no parentheses (e.g., a, b, h0, s0) SymbolId sym = symbols_.intern(name, SymbolKind::kConstant, 0); return bank_.makeTerm(sym, {}); } } } Literal parseLiteral() { bool is_positive = true; if (current_token_.type == TokenType::kNot) { is_positive = false; advance(); } TermId term = parseTerm(); return Literal{.atom = term, .is_positive = is_positive}; } }; ParsedProblem parseTptpString(const std::string& input, TermBank& bank, SymbolTable& symbols) { ParsedProblem problem; TptpParser parser(input, bank, symbols); parser.parseProblem(problem); return problem; } ParsedProblem parseTptpFile(const std::string& filename, TermBank& bank, SymbolTable& symbols) { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Failed to open file: " + filename); } std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); ParsedProblem problem = parseTptpString(content, bank, symbols); problem.source_file = filename; return problem; } std::vector prepareForProving(ParsedProblem& problem, TermBank& bank, SymbolTable& symbols) { std::vector final_clauses; for (auto& af : problem.formulas) { if (af.is_cnf) { for (auto& clause : af.clauses) { clause.rule = InferenceRule::kInput; clause.parent1 = kInvalidId; clause.parent2 = kInvalidId; final_clauses.push_back(std::move(clause)); } } } return final_clauses; } } // namespace atp