From addab6602d6120009e9477fd5550e3d5e749c909 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Thu, 24 Feb 2022 01:47:55 +0100 Subject: [PATCH 01/16] Add json printer for PAutomatonProduct + new PAutomaton::to_json format PAutomaton::from_json still uses old format - to be updated --- src/include/pdaaal/PAutomaton.h | 151 ++++-------------- src/include/pdaaal/PAutomatonProduct.h | 23 +++ src/include/pdaaal/PDA.h | 46 ++++++ src/pdaaal-bin/Verifier.h | 28 ++-- src/pdaaal-bin/main.cpp | 15 ++ src/pdaaal-bin/parsing/PAutomatonJsonParser.h | 135 ++++++++++++++++ src/pdaaal-bin/parsing/PdaJsonParser.h | 4 +- 7 files changed, 263 insertions(+), 139 deletions(-) create mode 100644 src/pdaaal-bin/parsing/PAutomatonJsonParser.h diff --git a/src/include/pdaaal/PAutomaton.h b/src/include/pdaaal/PAutomaton.h index 89e9fbf..e408f49 100644 --- a/src/include/pdaaal/PAutomaton.h +++ b/src/include/pdaaal/PAutomaton.h @@ -60,6 +60,9 @@ namespace pdaaal { PAutomaton(const pda_t& pda, const std::vector& special_initial_states, bool special_accepting = true) : parent_t(static_cast(pda), special_initial_states, special_accepting), _pda(pda) { }; + PAutomaton(PAutomaton&& other, const pda_t& pda) noexcept // Move constructor, but update reference to PDA. + : parent_t(std::move(other), static_cast(pda)), _pda(pda) {}; + [[nodiscard]] nlohmann::json to_json(const std::string& name = "P-automaton") const { nlohmann::json j; j[name] = *this; @@ -90,14 +93,19 @@ namespace pdaaal { } } - [[nodiscard]] state_t get_state(size_t id) const { + [[nodiscard]] std::optional get_state_optional(size_t id) const { if constexpr (skip_state_mapping) { return id; } else { if (id < _pda.states().size()) { return _pda.get_state(id); } else { - return static_cast*>(this)->get_state(id - _pda.states().size()); + auto offset = id - _pda.states().size(); + if (offset < this->state_map_size()) { + return static_cast*>(this)->get_state(offset); + } else { + return std::nullopt; + } } } } @@ -114,142 +122,39 @@ namespace pdaaal { template PAutomaton(const PDA&, const std::vector&, bool special_accepting = true) -> PAutomaton; - class PAutomatonJsonParser { - public: - template - static auto parse(const std::string& file, pda_t& pda, const std::string& name = "P-automaton") { - std::ifstream file_stream(file); - if (!file_stream.is_open()) { - std::stringstream error; - error << "Could not open " << name << " file: " << file << std::endl; - throw std::runtime_error(error.str()); - } - return parse(file_stream, pda, name); - } - template - static auto parse(std::istream& istream, pda_t& pda, const std::string& name = "P-automaton") { - json j; - istream >> j; - return from_json(j[name], pda); - } - template - static auto from_json(const json& j, PDA& pda) { - return from_json(j, pda, [](const std::string& s) { return s; }); - } - template - static auto from_json(const json& j, PDA& pda) { - return from_json(j, pda, [](const std::string& s) -> size_t { return std::stoul(s); }); - } - private: - template - static auto from_json(const json& j, - PDA& pda, - const std::function& state_mapping) { - // TODO: Proper error checking and handling.! - auto iterate_states = [&j,&state_mapping](const std::function& fn) { - if constexpr (skip_state_mapping) { - assert(j["states"].is_array()); - size_t i = 0; - for (const auto& j_state : j["states"]) { - fn(i, j_state); - ++i; - } - } else { - assert(j["states"].is_object()); - for (const auto& [name,j_state] : j["states"].items()) { - fn(state_mapping(name), j_state); - } - } - }; - std::vector accepting_initial_states; - std20::unordered_set accepting_extra_states; - iterate_states([&pda,&accepting_initial_states,&accepting_extra_states](const state_t& state, const json& j_state){ - auto [exists, id] = pda.exists_state(state); - if (exists) { - if (j_state.contains("accepting") && j_state["accepting"].get()) { - accepting_initial_states.emplace_back(id); - } - assert(j_state.contains("initial") && j_state["initial"].get()); - } else { - if (j_state.contains("accepting") && j_state["accepting"].get()) { - accepting_extra_states.emplace(state); - } - assert(!(j_state.contains("initial") && j_state["initial"].get())); - } - }); - std::sort(accepting_initial_states.begin(), accepting_initial_states.end()); - accepting_initial_states.erase(std::unique(accepting_initial_states.begin(), accepting_initial_states.end()), accepting_initial_states.end()); - PAutomaton automaton(pda, accepting_initial_states, true); - - auto state_to_id = [&automaton,&accepting_extra_states](const state_t& state){ - auto [exists, id] = automaton.exists_state(state); - if (!exists) { - id = automaton.add_state(false, accepting_extra_states.contains(state)); - auto id2 = automaton.insert_state(state); - assert(id == id2); - } - return id; - }; - - iterate_states([&state_to_id,&automaton,&state_mapping,&pda](const state_t& state, const json& j_state){ - size_t from = state_to_id(state); - for (const auto& edge : j_state["edges"]) { - size_t to; - if constexpr (skip_state_mapping) { - assert(edge["to"].is_number_unsigned()); - to = state_to_id(edge["to"].get()); - } else { - assert(edge["to"].is_string()); - to = state_to_id(state_mapping(edge["to"].get())); - } - auto label_string = edge["label"].get(); - if (label_string.empty()) { - automaton.add_epsilon_edge(from,to); - } else { - automaton.add_edge(from,to,pda.insert_label(label_string)); - } - } - }); - return automaton; - } - }; - template void to_json(json& j, const PAutomaton& automaton) { j = json::object(); size_t num_pda_states = automaton.pda().states().size(); - auto state_to_string = [&automaton](size_t state){ - return details::label_to_string(automaton.get_state(state)); + auto state_to_json_value = [&automaton](size_t state) -> json { + if constexpr(skip_state_mapping) { + return state; + } else { + if (auto s = automaton.get_state_optional(state); s) { + return details::label_to_string(s.value()); + } + return state; + } }; - json j_states; + json j_edges = json::array(); for (const auto& state : automaton.states()) { - auto j_state = json::object(); + auto j_from = state_to_json_value(state->_id); if (state->_id < num_pda_states) { - j_state["initial"] = true; + j["initial"].emplace_back(j_from); } if (state->_accepting) { - j_state["accepting"] = true; + j["accepting"].emplace_back(j_from); } - j_state["edges"] = json::array(); for (const auto& [to, labels] : state->_edges) { for (const auto& [label,tw] : labels) { - json edge; - if constexpr (skip_state_mapping) { - edge["to"] = to; - } else { - edge["to"] = state_to_string(to); - } - edge["label"] = label == automaton.epsilon ? "" : details::label_to_string(automaton.get_symbol(label)); - j_state["edges"].emplace_back(edge); + j_edges.emplace_back(json::array( + {j_from, + label == automaton.epsilon ? "" : details::label_to_string(automaton.get_symbol(label)), + state_to_json_value(to)})); } } - if constexpr (skip_state_mapping) { - j_states.emplace_back(j_state); - } else { - j_states[state_to_string(state->_id)] = j_state; - } } - j["states"] = j_states; + j["edges"] = j_edges; } } diff --git a/src/include/pdaaal/PAutomatonProduct.h b/src/include/pdaaal/PAutomatonProduct.h index b105007..4daf19d 100644 --- a/src/include/pdaaal/PAutomatonProduct.h +++ b/src/include/pdaaal/PAutomatonProduct.h @@ -151,6 +151,12 @@ namespace pdaaal { } } + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json j; + j = *this; + return j; + } + private: template bool add_edge(size_t from, uint32_t label, size_t to, internal::edge_annotation_t trace, @@ -370,11 +376,28 @@ namespace pdaaal { const NFA& final_nfa, const std::vector& final_states) -> PAutomatonProduct,PAutomaton,W>; + template + PAutomatonProduct(const PDA& pda, + PAutomaton&& initial, + PAutomaton&& final) + -> PAutomatonProduct,PAutomaton,W,trace_info_type>; + template PAutomatonProduct(const PDA& pda, internal::PAutomaton initial, internal::PAutomaton final) -> PAutomatonProduct,internal::PAutomaton,W,trace_info_type>; + template + void to_json(json& j, const PAutomatonProduct,PAutomaton, W, trace_info_type>& instance) { + j = json::array(); + j.emplace_back(json::object()); + details::params_state_names(j.back(), instance.pda()); + details::params_weight_type(j.back(), instance.pda()); + j.emplace_back(instance.pda()); + j.emplace_back(instance.initial_automaton()); + j.emplace_back(instance.final_automaton()); + } + } #endif //PDAAAL_PAUTOMATONPRODUCT_H diff --git a/src/include/pdaaal/PDA.h b/src/include/pdaaal/PDA.h index b6f3a81..cb85d7b 100644 --- a/src/include/pdaaal/PDA.h +++ b/src/include/pdaaal/PDA.h @@ -383,6 +383,52 @@ namespace pdaaal { j["states"] = j_states; } + namespace details { + template + void params_state_names(nlohmann::json& j, const PDA&) { + j["state-names"] = false; + } + template + void params_state_names(nlohmann::json& j, const PDA&) { + j["state-names"] = true; + } + template + void params_weight_type(nlohmann::json& j, const PDA,fut::type::vector,state_t,skip_state_mapping>&) { + j["weight-type"] = "none"; + } + template + void params_weight_type(nlohmann::json& j, const PDA>,fut::type::vector,state_t,skip_state_mapping>&) { + static_assert(weight>::is_weight); + j["weight-type"] = json::array(); + if constexpr(weight>::is_signed) { + j["weight-type"].emplace_back("int"); + } else { + j["weight-type"].emplace_back("uint"); + } + j["weight-type"].emplace_back(N); + } + template + void params_weight_type(nlohmann::json& j, const PDA>,fut::type::vector,state_t,skip_state_mapping>&) { + static_assert(weight>::is_weight); + j["weight-type"] = json::array(); + if constexpr(weight>::is_signed) { + j["weight-type"].emplace_back("int"); + } else { + j["weight-type"].emplace_back("uint"); + } + } + template + void params_weight_type(nlohmann::json& j, const PDA&) { + if constexpr(W::is_weight) { + if constexpr(W::is_signed) { + j["weight-type"] = "int"; + } else { + j["weight-type"] = "uint"; + } + } + } + } + } #endif /* PDAAAL_PDA_H */ diff --git a/src/pdaaal-bin/Verifier.h b/src/pdaaal-bin/Verifier.h index e4cc599..7ec9bec 100644 --- a/src/pdaaal-bin/Verifier.h +++ b/src/pdaaal-bin/Verifier.h @@ -28,6 +28,7 @@ #define PDAAAL_VERIFIER_H #include "parsing/PAutomatonParser.h" +#include "parsing/PAutomatonJsonParser.h" #include namespace pdaaal { @@ -84,6 +85,17 @@ namespace pdaaal { } [[nodiscard]] const po::options_description& options() const { return verification_options; } + template + auto get_product(PDA_T& pda) { + auto initial_p_automaton = json_automata ? + PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : + PAutomatonParser::parse_file(initial_pa_file, pda); + auto final_p_automaton = json_automata ? + PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : + PAutomatonParser::parse_file(final_pa_file, pda); + return PAutomatonProduct(pda, std::move(initial_p_automaton), std::move(final_p_automaton)); + } + template void verify(PDA_T& pda) { using pda_t = std20::remove_cvref_t; @@ -94,13 +106,7 @@ namespace pdaaal { case Trace_Type::None: case Trace_Type::Any: case Trace_Type::Shortest: { - auto initial_p_automaton = json_automata ? - PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : - PAutomatonParser::parse_file(initial_pa_file, pda); - auto final_p_automaton = json_automata ? - PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : - PAutomatonParser::parse_file(final_pa_file, pda); - PAutomatonProduct instance(pda, std::move(initial_p_automaton), std::move(final_p_automaton)); + auto instance = get_product(pda); switch (engine) { case 1: { std::cout << "Using post*" << std::endl; @@ -188,13 +194,7 @@ namespace pdaaal { } case Trace_Type::Longest: case Trace_Type::ShortestFixedPoint: { - auto initial_p_automaton = json_automata ? - PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : - PAutomatonParser::parse_file(initial_pa_file, pda); - auto final_p_automaton = json_automata ? - PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : - PAutomatonParser::parse_file(final_pa_file, pda); - PAutomatonProduct instance(pda, std::move(initial_p_automaton), std::move(final_p_automaton)); + auto instance = get_product(pda); switch (engine) { case 1: case 3: { diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index 72a960f..bf287d2 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -48,10 +48,12 @@ int main(int argc, const char** argv) { bool no_parser_warnings = false; bool silent = false; bool print_pda_json = false; + std::string solver_instance_file; output.add_options() ("disable-parser-warnings,W", po::bool_switch(&no_parser_warnings), "Disable warnings from parser.") ("silent,s", po::bool_switch(&silent), "Disables non-essential output (implies -W).") ("print-pda-json", po::bool_switch(&print_pda_json), "Print PDA in JSON format to terminal.") // TODO: This is currently mostly a debug option. Make it useful! + ("print-solver-instance", po::value(&solver_instance_file), "Print SolverInstance in JSON format to file.") ; opts.add(parsing.options()); opts.add(verifier.options()); @@ -86,6 +88,19 @@ int main(int argc, const char** argv) { }, pda_variant); return 0; // TODO: What else.? } + if (!solver_instance_file.empty()) { + std::ofstream out(solver_instance_file); + if (out.is_open()) { + std::visit([&out,&verifier](auto&& pda){ + auto instance = verifier.get_product(std::forward(pda)); + out << instance.to_json().dump() << std::endl; + }, pda_variant); + } else { + std::cerr << "Could not open --print-solver-instance\"" << solver_instance_file << "\" for writing" << std::endl; + exit(-1); + } + return 0; + } std::visit([](auto&& pda){ std::cout << "States: " << pda.states().size() << ". Labels: " << pda.number_of_labels() << std::endl; }, pda_variant); diff --git a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h new file mode 100644 index 0000000..9196611 --- /dev/null +++ b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h @@ -0,0 +1,135 @@ +/* + * 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 . + */ + +/* + * Copyright Morten K. Schou + */ + +/* + * File: PAutomatonJsonParser.h + * Author: Morten K. Schou + * + * Created on 23-02-2022. + */ + +#ifndef PDAAAL_PAUTOMATONJSONPARSER_H +#define PDAAAL_PAUTOMATONJSONPARSER_H + +#include + +namespace pdaaal { + + class PAutomatonJsonParser { + public: + template + static auto parse(const std::string& file, pda_t& pda, const std::string& name = "P-automaton") { + std::ifstream file_stream(file); + if (!file_stream.is_open()) { + std::stringstream error; + error << "Could not open " << name << " file: " << file << std::endl; + throw std::runtime_error(error.str()); + } + return parse(file_stream, pda, name); + } + template + static auto parse(std::istream& istream, pda_t& pda, const std::string& name = "P-automaton") { + json j; + istream >> j; + return from_json(j[name], pda); + } + template + static auto from_json(const json& j, PDA& pda) { + return from_json(j, pda, [](const std::string& s) { return s; }); + } + template + static auto from_json(const json& j, PDA& pda) { + return from_json(j, pda, [](const std::string& s) -> size_t { return std::stoul(s); }); + } + private: + template + static auto from_json(const json& j, + PDA& pda, + const std::function& state_mapping) { + // TODO: Proper error checking and handling.! + auto iterate_states = [&j,&state_mapping](const std::function& fn) { + if constexpr (skip_state_mapping) { + assert(j["states"].is_array()); + size_t i = 0; + for (const auto& j_state : j["states"]) { + fn(i, j_state); + ++i; + } + } else { + assert(j["states"].is_object()); + for (const auto& [name,j_state] : j["states"].items()) { + fn(state_mapping(name), j_state); + } + } + }; + std::vector accepting_initial_states; + std20::unordered_set accepting_extra_states; + iterate_states([&pda,&accepting_initial_states,&accepting_extra_states](const state_t& state, const json& j_state){ + auto [exists, id] = pda.exists_state(state); + if (exists) { + if (j_state.contains("accepting") && j_state["accepting"].get()) { + accepting_initial_states.emplace_back(id); + } + assert(j_state.contains("initial") && j_state["initial"].get()); + } else { + if (j_state.contains("accepting") && j_state["accepting"].get()) { + accepting_extra_states.emplace(state); + } + assert(!(j_state.contains("initial") && j_state["initial"].get())); + } + }); + std::sort(accepting_initial_states.begin(), accepting_initial_states.end()); + accepting_initial_states.erase(std::unique(accepting_initial_states.begin(), accepting_initial_states.end()), accepting_initial_states.end()); + PAutomaton automaton(pda, accepting_initial_states, true); + + auto state_to_id = [&automaton,&accepting_extra_states](const state_t& state){ + auto [exists, id] = automaton.exists_state(state); + if (!exists) { + id = automaton.add_state(false, accepting_extra_states.contains(state)); + auto id2 = automaton.insert_state(state); + assert(id == id2); + } + return id; + }; + + iterate_states([&state_to_id,&automaton,&state_mapping,&pda](const state_t& state, const json& j_state){ + size_t from = state_to_id(state); + for (const auto& edge : j_state["edges"]) { + size_t to; + if constexpr (skip_state_mapping) { + assert(edge["to"].is_number_unsigned()); + to = state_to_id(edge["to"].get()); + } else { + assert(edge["to"].is_string()); + to = state_to_id(state_mapping(edge["to"].get())); + } + auto label_string = edge["label"].get(); + if (label_string.empty()) { + automaton.add_epsilon_edge(from,to); + } else { + automaton.add_edge(from,to,pda.insert_label(label_string)); + } + } + }); + return automaton; + } + }; +}; + +#endif //PDAAAL_PAUTOMATONJSONPARSER_H diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index 574029f..bd272e5 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -202,7 +202,7 @@ namespace pdaaal { template // 'current_key' is the key to use. 'alternatives' are any other keys using the same flag in the same context (i.e. a one_of(current_key, alternatives...) requirement). bool handle_key() { - static_assert(flag == context::FLAG_1 || flag == context::FLAG_2 || flag == PdaaalSAXHandler::context::FLAG_3, + static_assert(flag == context::FLAG_1 || flag == context::FLAG_2 || flag == context::FLAG_3, "Template parameter flag must be a single key, not a union or empty."); static_assert(((context::get_key(type, flag) == current_key) || ... || (context::get_key(type, flag) == alternatives)), "The result of get_key(type, flag) must match 'key' or one of the alternatives"); @@ -242,7 +242,7 @@ namespace pdaaal { } bool null() { - switch (this->last_key) { + switch (last_key) { case keys::unknown: break; default: From be1b161d585ba5d496689408df7c9688900fc372 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Thu, 24 Feb 2022 01:25:28 +0100 Subject: [PATCH 02/16] Move PAutomatonJson test + add change test to new format (fails) --- CMakeLists.txt | 1 + test/CMakeLists.txt | 1 + test/PAutomaton_test.cpp | 30 --------------- test/Parser_test.cpp | 82 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 test/Parser_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d48b2ce..e2fc2bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ if(BUILD_TESTING AND PDAAAL_BuildTests) add_test(NAME AutomatonPath_test COMMAND AutomatonPath_test) if (PDAAAL_Build_Main) # We need the dependencies for pdaaal-bin to test it. + add_test(NAME Parser_test COMMAND Parser_test) add_test(NAME Verification_test COMMAND Verification_test) endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 51b770f..5642f76 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ endforeach() # Tests for pdaaal-bin (stand-alone binary) if (PDAAAL_Build_Main) # We need the dependencies for pdaaal-bin to test it. set(PDAAAL_bin_test_sources + Parser_test.cpp Verification_test.cpp ) foreach(test_source_file ${PDAAAL_bin_test_sources}) diff --git a/test/PAutomaton_test.cpp b/test/PAutomaton_test.cpp index d386df3..4cd95ce 100644 --- a/test/PAutomaton_test.cpp +++ b/test/PAutomaton_test.cpp @@ -34,36 +34,6 @@ using namespace pdaaal; -BOOST_AUTO_TEST_CASE(PAutomatonFromJsonTest) -{ - std::unordered_set labels{"A"}; - PDA pda(labels); - pda.add_rule(0, 0, POP, "*", "A"); - std::istringstream automaton_stream(R"({"P-automaton":{ - "states":[ - {"edges":[{"label":"A","to":1}],"initial":true}, - {"edges":[{"label":"A","to":2}]}, - {"accepting":true,"edges":[]} - ] - }})"); - auto automaton = PAutomatonJsonParser::parse<>(automaton_stream, pda); - std::vector stack; stack.emplace_back(0); - bool result = Solver::post_star_accepts(automaton, 0, stack); - BOOST_CHECK(result); -} - -BOOST_AUTO_TEST_CASE(PAutomatonToJsonTest) -{ - std::unordered_set labels{"A"}; - PDA pda(labels); - pda.add_rule(0, 0, POP, "*", "A"); - std::vector init_stack{"A", "A"}; - PAutomaton automaton(pda, 0, init_stack); - auto j = automaton.to_json(); - BOOST_TEST_MESSAGE(j.dump()); - BOOST_CHECK_EQUAL(j.dump(), R"({"P-automaton":{"states":[{"edges":[{"label":"A","to":1}],"initial":true},{"edges":[{"label":"A","to":2}]},{"accepting":true,"edges":[]}]}})"); -} - BOOST_AUTO_TEST_CASE(Dijkstra_Test_1) { using trace_t = internal::trace_t; diff --git a/test/Parser_test.cpp b/test/Parser_test.cpp new file mode 100644 index 0000000..4707b3b --- /dev/null +++ b/test/Parser_test.cpp @@ -0,0 +1,82 @@ +/* + * 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 . + */ + +/* + * Copyright Morten K. Schou + */ + +/* + * File: Parser_test + * Author: Morten K. Schou + * + * Created on 24-02-2022. + */ + +#define BOOST_TEST_MODULE Parser_test + +#include +#include +#include + +using namespace pdaaal; + +BOOST_AUTO_TEST_CASE(PAutomatonFromJson_OldFormat_Test) +{ + std::unordered_set labels{"A"}; + PDA pda(labels); + pda.add_rule(0, 0, POP, "*", "A"); + std::istringstream automaton_stream(R"({"P-automaton":{ + "states":[ + {"edges":[{"label":"A","to":1}],"initial":true}, + {"edges":[{"label":"A","to":2}]}, + {"accepting":true,"edges":[]} + ] + }})"); + auto automaton = PAutomatonJsonParser::parse<>(automaton_stream, pda); + std::vector stack; stack.emplace_back(0); + bool result = Solver::post_star_accepts(automaton, 0, stack); + BOOST_CHECK(result); +} + +BOOST_AUTO_TEST_CASE(PAutomatonFromJson_NewFormat_Test_Fails) +{ + std::unordered_set labels{"A"}; + PDA pda(labels); + pda.add_rule(0, 0, POP, "*", "A"); + std::istringstream automaton_stream(R"({"P-automaton":{ + "initial":[0], + "accepting":[2], + "edges":[ + [0,"A",1], + [1,"A",2] + ] + }})"); + auto automaton = PAutomatonJsonParser::parse<>(automaton_stream, pda); + std::vector stack; stack.emplace_back(0); + bool result = Solver::post_star_accepts(automaton, 0, stack); + BOOST_CHECK(result); +} + +BOOST_AUTO_TEST_CASE(PAutomatonToJsonTest) +{ + std::unordered_set labels{"A"}; + PDA pda(labels); + pda.add_rule(0, 0, POP, "*", "A"); + std::vector init_stack{"A", "A"}; + PAutomaton automaton(pda, 0, init_stack); + auto j = automaton.to_json(); + BOOST_TEST_MESSAGE(j.dump()); + BOOST_CHECK_EQUAL(j.dump(), R"({"P-automaton":{"accepting":[2],"edges":[[0,"A",1],[1,"A",2]],"initial":[0]}})"); +} From 71b432ab88ac5ea0ae1803138214e8dc71680f30 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Thu, 24 Feb 2022 01:05:11 +0100 Subject: [PATCH 03/16] Add utils::flags and SaxHandlerHelpers + enable embedded PdaSaxHandler --- src/include/pdaaal/utils/flags.h | 95 ++++++++++++ src/pdaaal-bin/parsing/Parsing.h | 12 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 160 ++++++++++----------- src/pdaaal-bin/parsing/SaxHandlerHelpers.h | 93 ++++++++++++ 4 files changed, 272 insertions(+), 88 deletions(-) create mode 100644 src/include/pdaaal/utils/flags.h create mode 100644 src/pdaaal-bin/parsing/SaxHandlerHelpers.h diff --git a/src/include/pdaaal/utils/flags.h b/src/include/pdaaal/utils/flags.h new file mode 100644 index 0000000..de0a3b1 --- /dev/null +++ b/src/include/pdaaal/utils/flags.h @@ -0,0 +1,95 @@ +/* + * 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 . + */ + +/* + * Copyright Morten K. Schou + */ + +/* + * File: flags.h + * Author: Morten K. Schou + * + * Created on 24-02-2022. + */ + +#ifndef PDAAAL_FLAGS_H +#define PDAAAL_FLAGS_H + +#include +#include +#include +#include + +namespace pdaaal::utils { + template struct flags { + static_assert(N <= 64, "At most 64 flags supported."); + using flag_t = std::conditional_t>>; + static constexpr void add(flag_t& mask, flag_t flag) { mask |= flag; } + static constexpr void remove(flag_t& mask, flag_t flag) { mask &= ~flag; } + static constexpr flag_t select(flag_t mask, flag_t flag) { return mask & flag; } + static constexpr bool contains(flag_t mask, flag_t flag) { return select(mask, flag) == flag; } + static constexpr bool is_single_flag(flag_t x) { return x != 0 && (x & (x - 1)) == 0; } + + template + static constexpr flag_t flag() { + static_assert(I <= N); + if constexpr(I==0) { + return 0; + } else { + return (flag_t)1<<(I-1); + } + } + static constexpr flag_t no_flags = flag<0>(); + template + static constexpr flag_t fill() { + static_assert(I <= N); + if constexpr(I==0) { + return no_flags; + } else if constexpr(I==N) { + return flag() | fill(); + } else { + return ((flag_t)1< split_to_single_flags(flag_t mask) { + std::vector flags; + for (std::size_t i = 0; i < N; ++i) { + flag_t f = (flag_t)1<, true>::pda_t, - PdaaalSAXHandler, false>::pda_t, - PdaaalSAXHandler, true>::pda_t, - PdaaalSAXHandler, false>::pda_t, - PdaaalSAXHandler, true>::pda_t, - PdaaalSAXHandler, false>::pda_t>; + PdaSaxHandler, true>::pda_t, + PdaSaxHandler, false>::pda_t, + PdaSaxHandler, true>::pda_t, + PdaSaxHandler, false>::pda_t, + PdaSaxHandler, true>::pda_t, + PdaSaxHandler, false>::pda_t>; explicit Parsing(const std::string& caption = "Input Options") : input_options{caption} { input_options.add_options() diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index bd272e5..e7b9047 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -27,6 +27,7 @@ #ifndef PDAAAL_PDAJSONPARSER_H #define PDAAAL_PDAJSONPARSER_H +#include "SaxHandlerHelpers.h" #include #include #include @@ -40,8 +41,8 @@ using json = nlohmann::json; namespace pdaaal { - template , bool use_state_names = true> - class PdaaalSAXHandler { + template , bool use_state_names = true, bool embedded_parser = false> + class PdaSaxHandler : public SAXHandlerBase { public: using pda_t = std::conditional_t, @@ -92,8 +93,21 @@ namespace pdaaal { } return s; } - struct context { - enum class context_type : uint32_t { unknown, initial, pda, state_array, states_object, state, rule_array, rule }; + struct context : public SAXHandlerContext<3> { + using parent_t = SAXHandlerContext<3>; + using key_flag = parent_t::flag_t; + using parent_t::flag; + using parent_t::fill; + using parent_t::is_single_flag; + static constexpr auto FLAG_1 = flag<1>(); + static constexpr auto FLAG_2 = flag<2>(); + static constexpr auto FLAG_3 = flag<3>(); + static constexpr auto NO_FLAGS = fill<0>(); + static constexpr auto REQUIRES_1 = fill<1>(); + static constexpr auto REQUIRES_2 = fill<2>(); + static constexpr auto REQUIRES_3 = fill<3>(); + + enum class context_type : uint8_t { unknown, initial, pda, state_array, states_object, state, rule_array, rule }; friend constexpr std::ostream& operator<<(std::ostream& s, context_type t) { switch (t) { case context_type::unknown: @@ -123,60 +137,39 @@ namespace pdaaal { } return s; } - enum key_flag : uint32_t { - NO_FLAGS = 0, - FLAG_1 = 1, - FLAG_2 = 2, - FLAG_3 = 4, - // Required values for each object type. - REQUIRES_0 = NO_FLAGS, - REQUIRES_1 = FLAG_1, - REQUIRES_2 = FLAG_1 | FLAG_2, - REQUIRES_3 = FLAG_1 | FLAG_2 | FLAG_3 - }; - static constexpr std::array all_flags{key_flag::FLAG_1, key_flag::FLAG_2, key_flag::FLAG_3}; - context_type type; - key_flag values_left; + constexpr context(context_type type, key_flag flags) noexcept : parent_t(flags), type(type) {}; - constexpr void got_value(key_flag value) { - values_left = static_cast(static_cast(values_left) & ~static_cast(value)); - } - [[nodiscard]] constexpr bool needs_value(key_flag value) const { - return static_cast(value) == (static_cast(values_left) & static_cast(value)); - } - [[nodiscard]] constexpr bool missing_keys() const { - return values_left != NO_FLAGS; - } + context_type type; static constexpr keys get_key(context_type context_type, key_flag flag) { switch (context_type) { case context_type::initial: - if (flag == context::key_flag::FLAG_1) { + if (flag == FLAG_1) { return keys::pda; } break; case context_type::pda: - if (flag == context::key_flag::FLAG_1) { + if (flag == FLAG_1) { return keys::states; } break; case context_type::rule: if constexpr (expect_weight) { switch (flag) { - case key_flag::FLAG_1: + case FLAG_1: return keys::to; - case key_flag::FLAG_2: + case FLAG_2: return keys::pop; // NOTE: Also keys::swap and keys::push - case key_flag::FLAG_3: + case FLAG_3: return keys::weight; default: break; } } else { switch (flag) { - case key_flag::FLAG_1: + case FLAG_1: return keys::to; - case key_flag::FLAG_2: + case FLAG_2: return keys::pop; // NOTE: Also keys::swap and keys::push default: break; @@ -202,14 +195,13 @@ namespace pdaaal { template // 'current_key' is the key to use. 'alternatives' are any other keys using the same flag in the same context (i.e. a one_of(current_key, alternatives...) requirement). bool handle_key() { - static_assert(flag == context::FLAG_1 || flag == context::FLAG_2 || flag == context::FLAG_3, - "Template parameter flag must be a single key, not a union or empty."); + static_assert(context::is_single_flag(flag), "Template parameter flag must be a single key, not a union or empty."); static_assert(((context::get_key(type, flag) == current_key) || ... || (context::get_key(type, flag) == alternatives)), "The result of get_key(type, flag) must match 'key' or one of the alternatives"); if (!context_stack.top().needs_value(flag)) { - errors << "Duplicate definition of key: \"" << current_key; - ((errors << "\"/\"" << alternatives), ...); - errors << "\" in " << type << " object. " << std::endl; + errors() << "Duplicate definition of key: \"" << current_key; + ((errors() << "\"/\"" << alternatives), ...); + errors() << "\" in " << type << " object. " << std::endl; return false; } context_stack.top().got_value(flag); @@ -219,7 +211,6 @@ namespace pdaaal { std::stack context_stack; keys last_key = keys::none; - std::ostream& errors; build_pda_t build_pda; @@ -228,6 +219,12 @@ namespace pdaaal { bool current_wildcard = false; typename internal::PDA::rule_t current_rule; + void init() { + if constexpr(embedded_parser) { + context_stack.push(initial_context); + last_key = keys::pda; + } + } public: using number_integer_t = typename json::number_integer_t; using number_unsigned_t = typename json::number_unsigned_t; @@ -235,7 +232,8 @@ namespace pdaaal { using string_t = typename json::string_t; using binary_t = typename json::binary_t; - explicit PdaaalSAXHandler(std::ostream& errors = std::cerr) : errors(errors) {}; + explicit PdaSaxHandler(std::ostream& errors = std::cerr) : SAXHandlerBase(errors) { init(); }; + explicit PdaSaxHandler(const SAXHandlerBase& base) : SAXHandlerBase(base) { init(); }; pda_t get_pda() { return pda_t(std::move(build_pda)); @@ -246,7 +244,7 @@ namespace pdaaal { case keys::unknown: break; default: - errors << "error: Unexpected null value after key: " << last_key << std::endl; + errors() << "error: Unexpected null value after key: " << last_key << std::endl; return false; } return true; @@ -256,7 +254,7 @@ namespace pdaaal { case keys::unknown: break; default: - errors << "error: Unexpected boolean value: " << value << " after key: " << last_key << std::endl; + errors() << "error: Unexpected boolean value: " << value << " after key: " << last_key << std::endl; return false; } return true; @@ -268,18 +266,18 @@ namespace pdaaal { case keys::weight: if constexpr (expect_weight && std::numeric_limits::is_signed) { if (value >= std::numeric_limits::max()) { - errors << "error: Integer value " << value << " is too large. Maximum value is: " << std::numeric_limits::max()-1 << std::endl; + errors() << "error: Integer value " << value << " is too large. Maximum value is: " << std::numeric_limits::max()-1 << std::endl; return false; } if (value <= std::numeric_limits::min()) { - errors << "error: Integer value " << value << " is too low. Minimum value is: " << std::numeric_limits::min()+1 << std::endl; + errors() << "error: Integer value " << value << " is too low. Minimum value is: " << std::numeric_limits::min()+1 << std::endl; return false; } current_rule._weight = value; break; } default: - errors << "error: Integer value: " << value << " found after key:" << last_key << std::endl; + errors() << "error: Integer value: " << value << " found after key:" << last_key << std::endl; return false; } return true; @@ -288,7 +286,7 @@ namespace pdaaal { switch (last_key) { case keys::to: if constexpr(use_state_names) { - errors << "error: Rule destination was numeric: " << value << ", but string state names are used." << std::endl; + errors() << "error: Rule destination was numeric: " << value << ", but string state names are used." << std::endl; return false; } else { current_rule._to = value; @@ -297,7 +295,7 @@ namespace pdaaal { case keys::weight: if constexpr (expect_weight) { // TODO: Parameterize on weight type... if (value >= std::numeric_limits::max()) { - errors << "error: Unsigned value " << value << " is too large. Maximum value is: " << std::numeric_limits::max()-1 << std::endl; + errors() << "error: Unsigned value " << value << " is too large. Maximum value is: " << std::numeric_limits::max()-1 << std::endl; return false; } current_rule._weight = value; @@ -306,7 +304,7 @@ namespace pdaaal { case keys::unknown: break; default: - errors << "error: Unsigned value: " << value << " found after key: " << last_key << std::endl; + errors() << "error: Unsigned value: " << value << " found after key: " << last_key << std::endl; return false; } return true; @@ -316,14 +314,14 @@ namespace pdaaal { case keys::unknown: break; default: - errors << "error: Float value: " << value << " comes after key: " << last_key << std::endl; + errors() << "error: Float value: " << value << " comes after key: " << last_key << std::endl; return false; } return true; } bool string(string_t& value) { if (context_stack.empty()) { - errors << "error: Unexpected string value: \"" << value << "\" outside of object." << std::endl; + errors() << "error: Unexpected string value: \"" << value << "\" outside of object." << std::endl; return false; } switch (last_key) { @@ -332,7 +330,7 @@ namespace pdaaal { current_rule._to = build_pda.insert_state(value); break; } else { - errors << "error: Rule \"to\" state was string: " << value << ", but state names are disabled in this setting. Try with --state-names" << std::endl; + errors() << "error: Rule \"to\" state was string: " << value << ", but state names are disabled in this setting. Try with --state-names" << std::endl; return false; } case keys::pop: @@ -352,7 +350,7 @@ namespace pdaaal { break; default: case keys::none: - errors << "error: String value: " << value << " found after key:" << last_key << std::endl; + errors() << "error: String value: " << value << " found after key:" << last_key << std::endl; return false; } return true; @@ -361,7 +359,7 @@ namespace pdaaal { if (last_key == keys::unknown) { return true; } - errors << "error: Unexpected binary value found after key:" << last_key << std::endl; + errors() << "error: Unexpected binary value found after key:" << last_key << std::endl; return false; } bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { @@ -388,7 +386,7 @@ namespace pdaaal { context_stack.push(states_object); break; } else { - errors << "error: Found object after key: " << last_key << ", but state names are disabled in this setting. Try with --state-names." << std::endl; + errors() << "error: Found object after key: " << last_key << ", but state names are disabled in this setting. Try with --state-names." << std::endl; return false; } case keys::state_name: @@ -401,14 +399,14 @@ namespace pdaaal { context_stack.push(unknown_context); break; default: - errors << "error: Found object after key: " << last_key << std::endl; + errors() << "error: Found object after key: " << last_key << std::endl; return false; } return true; } bool key(string_t& key) { if (context_stack.empty()) { - errors << "Expected the start of an object before key: " << key << std::endl; + errors() << "Expected the start of an object before key: " << key << std::endl; return false; } switch (context_stack.top().type) { @@ -432,7 +430,7 @@ namespace pdaaal { current_from_state = build_pda.insert_state(key); break; } else { - errors << "error: Encountered state name: \"" << key << "\" in context: " << context_stack.top().type << ", but state names are disabled in this setting." << std::endl; + errors() << "error: Encountered state name: \"" << key << "\" in context: " << context_stack.top().type << ", but state names are disabled in this setting." << std::endl; return false; } case context::context_type::state: @@ -461,38 +459,36 @@ namespace pdaaal { break; } } - errors << "Unexpected key in operation object: " << key << std::endl; + errors() << "Unexpected key in operation object: " << key << std::endl; return false; } break; case context::context_type::unknown: break; default: - errors << "error: Encountered unexpected key: \"" << key << "\" in context: " << context_stack.top().type << std::endl; + errors() << "error: Encountered unexpected key: \"" << key << "\" in context: " << context_stack.top().type << std::endl; return false; } return true; } bool end_object() { if (context_stack.empty()) { - errors << "error: Unexpected end of object." << std::endl; + errors() << "error: Unexpected end of object." << std::endl; return false; } if (context_stack.top().missing_keys()) { - errors << "error: Missing key(s): "; + errors() << "error: Missing key(s): "; bool first = true; - for (const auto& flag : context::all_flags) { - if (context_stack.top().needs_value(flag)) { - if (!first) errors << ", "; - first = false; - auto key = context::get_key(context_stack.top().type, flag); - errors << key; - if (key == keys::pop) { - errors << "/" << keys::swap << "/" << keys::push; - } + for (const auto& flag : context_stack.top().get_missing_flags()) { + if (!first) errors() << ", "; + first = false; + auto key = context::get_key(context_stack.top().type, flag); + errors() << key; + if (key == keys::pop) { + errors() << "/" << keys::swap << "/" << keys::push; } } - errors << " in object: " << context_stack.top().type << std::endl; + errors() << " in object: " << context_stack.top().type << std::endl; return false; } switch (context_stack.top().type) { @@ -508,17 +504,22 @@ namespace pdaaal { break; } context_stack.pop(); + if constexpr(embedded_parser) { + if (context_stack.top().type == context::context_type::initial) { + return false; // Stop using this SAXHandler. + } + } return true; } bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { if (context_stack.empty()) { - errors << "error: Encountered start of array, but must start with an object." << std::endl; + errors() << "error: Encountered start of array, but must start with an object." << std::endl; return false; } switch (last_key) { case keys::states: if constexpr (use_state_names) { - errors << "Unexpected start of array after key " << last_key << ". Note that state names are used in this setting." << std::endl; + errors() << "Unexpected start of array after key " << last_key << ". Note that state names are used in this setting." << std::endl; return false; } else { context_stack.push(state_array); @@ -532,24 +533,19 @@ namespace pdaaal { context_stack.push(unknown_context); break; default: - errors << "Unexpected start of array after key " << last_key << std::endl; + errors() << "Unexpected start of array after key " << last_key << std::endl; return false; } return true; } bool end_array() { if (context_stack.empty()) { - errors << "error: Unexpected end of array." << std::endl; + errors() << "error: Unexpected end of array." << std::endl; return false; } context_stack.pop(); return true; } - bool parse_error(std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e) { - errors << "error at line " << location << " with last token " << last_token << ". " << std::endl; - errors << "\terror message: " << e.what() << std::endl; - return false; - } }; class PdaJSONParser { @@ -557,7 +553,7 @@ namespace pdaaal { template , bool use_state_names = true> static auto parse(std::istream& stream, std::ostream& /*warnings*/, json::input_format_t format = json::input_format_t::json) { std::stringstream es; // For errors; - PdaaalSAXHandler my_sax(es); + PdaSaxHandler my_sax(es); if (!json::sax_parse(stream, &my_sax, format)) { throw std::runtime_error(es.str()); } diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h new file mode 100644 index 0000000..a7c2a84 --- /dev/null +++ b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h @@ -0,0 +1,93 @@ +/* + * 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 . + */ + +/* + * Copyright Morten K. Schou + */ + +/* + * File: SaxHandlerHelpers.h + * Author: Morten K. Schou + * + * Created on 23-02-2022. + */ + +#ifndef PDAAAL_SAXHANDLERHELPERS_H +#define PDAAAL_SAXHANDLERHELPERS_H + +#include +#include +#include +#include +#include + +//using json = nlohmann::json; + +namespace pdaaal { + + // We wish to be able to stop parsing before the end of input, but still handle errors correctly. + // Keep track of whether the error stream was used, and this way distinguish between error and early return. + class SAXHandlerBase { + public: + explicit SAXHandlerBase(std::ostream& errors) : _errors(errors) {}; + + bool parse_error(std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e) { + errors() << "error at line " << location << " with last token " << last_token << ". " << std::endl; + errors() << "\terror message: " << e.what() << std::endl; + return false; + } + [[nodiscard]] bool is_errored() const { return _errored; } + + protected: + std::ostream& errors() { + _errored = true; + return _errors; + } + private: + std::ostream& _errors; + bool _errored = false; + }; + + template + class SAXHandlerContext : utils::flags { + using parent_t = utils::flags; + public: + using flag_t = typename parent_t::flag_t; + using parent_t::flag; + using parent_t::fill; + using parent_t::is_single_flag; + + explicit constexpr SAXHandlerContext(flag_t flags) noexcept : values_left(flags) {}; + + flag_t values_left; + constexpr void got_value(flag_t value) { + parent_t::remove(values_left, value); + } + [[nodiscard]] constexpr bool needs_value(flag_t value) const { + return parent_t::contains(values_left, value); + } + [[nodiscard]] constexpr bool missing_keys() const { + return values_left != parent_t::no_flags; + } + std::vector get_missing_flags() const { + return parent_t::split_to_single_flags(values_left); + } + + }; + + +} + +#endif //PDAAAL_SAXHANDLERHELPERS_H From d1b15dcbf4cc3592004f8bd5b07e7aa66781eebf Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Thu, 24 Feb 2022 15:38:21 +0100 Subject: [PATCH 04/16] Generalize flag_mask and object_context + namespace pdaaal::parsing --- src/include/pdaaal/Weight.h | 4 + src/include/pdaaal/utils/flags.h | 28 +++ src/pdaaal-bin/Verifier.h | 8 +- src/pdaaal-bin/main.cpp | 2 +- src/pdaaal-bin/parsing/PAutomatonJsonParser.h | 2 +- src/pdaaal-bin/parsing/PAutomatonParser.h | 2 +- src/pdaaal-bin/parsing/Parsing.cpp | 6 +- src/pdaaal-bin/parsing/Parsing.h | 2 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 231 +++++++++--------- src/pdaaal-bin/parsing/SaxHandlerHelpers.h | 36 +-- test/Parser_test.cpp | 4 +- test/Verification_test.cpp | 43 ++-- 12 files changed, 189 insertions(+), 179 deletions(-) diff --git a/src/include/pdaaal/Weight.h b/src/include/pdaaal/Weight.h index 7db32e6..f2f19c2 100644 --- a/src/include/pdaaal/Weight.h +++ b/src/include/pdaaal/Weight.h @@ -41,6 +41,7 @@ namespace pdaaal { struct weight_base { static constexpr bool is_weight = false; static constexpr bool is_signed = false; + static constexpr bool is_vector = false; }; } template struct weight : public details::weight_base { @@ -51,6 +52,7 @@ namespace pdaaal { using type = W; static constexpr bool is_weight = true; static constexpr bool is_signed = std::numeric_limits::is_signed; + static constexpr bool is_vector = false; static constexpr auto zero = []() -> type { return static_cast(0); }; }; template @@ -58,6 +60,7 @@ namespace pdaaal { using type = std::array; static constexpr bool is_weight = true; static constexpr bool is_signed = weight::is_signed; + static constexpr bool is_vector = false; static constexpr auto zero = []() -> type { std::array arr{}; arr.fill(weight::zero()); @@ -69,6 +72,7 @@ namespace pdaaal { using type = std::vector; static constexpr bool is_weight = true; static constexpr bool is_signed = weight::is_signed; + static constexpr bool is_vector = true; static constexpr auto zero = []() -> type { return type{}; }; // TODO: When C++20 arrives, use a constexpr vector instead }; diff --git a/src/include/pdaaal/utils/flags.h b/src/include/pdaaal/utils/flags.h index de0a3b1..8835897 100644 --- a/src/include/pdaaal/utils/flags.h +++ b/src/include/pdaaal/utils/flags.h @@ -33,6 +33,7 @@ #include namespace pdaaal::utils { + template struct flags { static_assert(N <= 64, "At most 64 flags supported."); using flag_t = std::conditional_t + class flag_mask : flags { + public: + using flag_t = typename flags::flag_t; + using flags::flag; + using flags::fill; + using flags::is_single_flag; + + explicit constexpr flag_mask(flag_t mask) noexcept : _mask(mask) {}; + + constexpr void got_flag(flag_t value) { + flags::remove(_mask, value); + } + [[nodiscard]] constexpr bool needs_flag(flag_t value) const { + return flags::contains(_mask, value); + } + [[nodiscard]] constexpr bool has_missing_flags() const { + return _mask != flags::no_flags; + } + std::vector get_missing_flags() const { + return flags::split_to_single_flags(_mask); + } +// private: + flag_t _mask; + }; + } #endif //PDAAAL_FLAGS_H diff --git a/src/pdaaal-bin/Verifier.h b/src/pdaaal-bin/Verifier.h index 7ec9bec..06522e9 100644 --- a/src/pdaaal-bin/Verifier.h +++ b/src/pdaaal-bin/Verifier.h @@ -88,11 +88,11 @@ namespace pdaaal { template auto get_product(PDA_T& pda) { auto initial_p_automaton = json_automata ? - PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : - PAutomatonParser::parse_file(initial_pa_file, pda); + parsing::PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : + parsing::PAutomatonParser::parse_file(initial_pa_file, pda); auto final_p_automaton = json_automata ? - PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : - PAutomatonParser::parse_file(final_pa_file, pda); + parsing::PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : + parsing::PAutomatonParser::parse_file(final_pa_file, pda); return PAutomatonProduct(pda, std::move(initial_p_automaton), std::move(final_p_automaton)); } diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index bf287d2..46a234f 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -41,7 +41,7 @@ int main(int argc, const char** argv) { ("help,h", "produce help message") ("version,v", "print version"); - Parsing parsing("Input Options"); + parsing::Parsing parsing("Input Options"); Verifier verifier("Verification Options"); po::options_description output("Output Options"); diff --git a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h index 9196611..0e77fe0 100644 --- a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h +++ b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h @@ -29,7 +29,7 @@ #include -namespace pdaaal { +namespace pdaaal::parsing { class PAutomatonJsonParser { public: diff --git a/src/pdaaal-bin/parsing/PAutomatonParser.h b/src/pdaaal-bin/parsing/PAutomatonParser.h index bcc141e..624d53c 100644 --- a/src/pdaaal-bin/parsing/PAutomatonParser.h +++ b/src/pdaaal-bin/parsing/PAutomatonParser.h @@ -31,7 +31,7 @@ #include #include -namespace pdaaal { +namespace pdaaal::parsing { // First define grammar for PAutomaton. // Escaped string diff --git a/src/pdaaal-bin/parsing/Parsing.cpp b/src/pdaaal-bin/parsing/Parsing.cpp index e2c39a5..ebc97d7 100644 --- a/src/pdaaal-bin/parsing/Parsing.cpp +++ b/src/pdaaal-bin/parsing/Parsing.cpp @@ -27,7 +27,7 @@ #include "Parsing.h" #include -namespace pdaaal { +namespace pdaaal::parsing { enum class input_format {PDAAAL, MOPED}; enum class weight_type {NONE, UINT, INT}; @@ -44,9 +44,9 @@ namespace pdaaal { template Parsing::pda_variant_t parse_stream_json_w(std::istream& stream, const parsing_options_t& parse_opts) { if (parse_opts.use_state_names) { - return PdaJSONParser::parse(stream, parse_opts.warnings); + return PdaJsonParser::parse(stream, parse_opts.warnings); } else { - return PdaJSONParser::parse(stream, parse_opts.warnings); + return PdaJsonParser::parse(stream, parse_opts.warnings); } } Parsing::pda_variant_t parse_stream_json(std::istream& stream, const parsing_options_t& parse_opts) { diff --git a/src/pdaaal-bin/parsing/Parsing.h b/src/pdaaal-bin/parsing/Parsing.h index d796eb2..08131fc 100644 --- a/src/pdaaal-bin/parsing/Parsing.h +++ b/src/pdaaal-bin/parsing/Parsing.h @@ -36,7 +36,7 @@ namespace po = boost::program_options; -namespace pdaaal { +namespace pdaaal::parsing { class Parsing { public: diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index e7b9047..1b814ec 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -39,7 +39,7 @@ using json = nlohmann::json; -namespace pdaaal { +namespace pdaaal::parsing { template , bool use_state_names = true, bool embedded_parser = false> class PdaSaxHandler : public SAXHandlerBase { @@ -93,118 +93,107 @@ namespace pdaaal { } return s; } - struct context : public SAXHandlerContext<3> { - using parent_t = SAXHandlerContext<3>; - using key_flag = parent_t::flag_t; - using parent_t::flag; - using parent_t::fill; - using parent_t::is_single_flag; - static constexpr auto FLAG_1 = flag<1>(); - static constexpr auto FLAG_2 = flag<2>(); - static constexpr auto FLAG_3 = flag<3>(); - static constexpr auto NO_FLAGS = fill<0>(); - static constexpr auto REQUIRES_1 = fill<1>(); - static constexpr auto REQUIRES_2 = fill<2>(); - static constexpr auto REQUIRES_3 = fill<3>(); - - enum class context_type : uint8_t { unknown, initial, pda, state_array, states_object, state, rule_array, rule }; - friend constexpr std::ostream& operator<<(std::ostream& s, context_type t) { - switch (t) { - case context_type::unknown: - s << ""; - break; - case context_type::initial: - s << "initial"; - break; - case context_type::pda: - s << "pda"; - break; - case context_type::state_array: - s << "states array"; - break; - case context_type::states_object: - s << "states object"; - break; - case context_type::state: - s << "state"; - break; - case context_type::rule_array: - s << "rule array"; - break; - case context_type::rule: - s << "rule"; - break; - } - return s; + enum class context_type : uint8_t { unknown, initial, pda, state_array, states_object, state, rule_array, rule }; + friend constexpr std::ostream& operator<<(std::ostream& s, context_type t) { + switch (t) { + case context_type::unknown: + s << ""; + break; + case context_type::initial: + s << "initial"; + break; + case context_type::pda: + s << "pda"; + break; + case context_type::state_array: + s << "states array"; + break; + case context_type::states_object: + s << "states object"; + break; + case context_type::state: + s << "state"; + break; + case context_type::rule_array: + s << "rule array"; + break; + case context_type::rule: + s << "rule"; + break; } - - constexpr context(context_type type, key_flag flags) noexcept : parent_t(flags), type(type) {}; - - context_type type; - static constexpr keys get_key(context_type context_type, key_flag flag) { - switch (context_type) { - case context_type::initial: - if (flag == FLAG_1) { - return keys::pda; - } - break; - case context_type::pda: - if (flag == FLAG_1) { - return keys::states; + return s; + } + using context = parser_object_context; + using key_flag = typename context::key_flag; + static constexpr auto FLAG_1 = context::template flag<1>(); + static constexpr auto FLAG_2 = context::template flag<2>(); + static constexpr auto FLAG_3 = context::template flag<3>(); + template static constexpr context make_context() { + return make_object_context(); + } + static constexpr keys get_key(context_type context_type, key_flag flag) { + switch (context_type) { + case context_type::initial: + if (flag == FLAG_1) { + return keys::pda; + } + break; + case context_type::pda: + if (flag == FLAG_1) { + return keys::states; + } + break; + case context_type::rule: + if constexpr (expect_weight) { + switch (flag) { + case FLAG_1: + return keys::to; + case FLAG_2: + return keys::pop; // NOTE: Also keys::swap and keys::push + case FLAG_3: + return keys::weight; + default: + break; } - break; - case context_type::rule: - if constexpr (expect_weight) { - switch (flag) { - case FLAG_1: - return keys::to; - case FLAG_2: - return keys::pop; // NOTE: Also keys::swap and keys::push - case FLAG_3: - return keys::weight; - default: - break; - } - } else { - switch (flag) { - case FLAG_1: - return keys::to; - case FLAG_2: - return keys::pop; // NOTE: Also keys::swap and keys::push - default: - break; - } + } else { + switch (flag) { + case FLAG_1: + return keys::to; + case FLAG_2: + return keys::pop; // NOTE: Also keys::swap and keys::push + default: + break; } - break; - default: - break; - } - assert(false); - return keys::unknown; + } + break; + default: + break; } - }; - constexpr static context unknown_context = {context::context_type::unknown, context::NO_FLAGS }; - constexpr static context initial_context = {context::context_type::initial, context::REQUIRES_1 }; - constexpr static context pda_context = {context::context_type::pda, context::REQUIRES_1 }; - constexpr static context state_array = {context::context_type::state_array, context::NO_FLAGS }; - constexpr static context states_object = {context::context_type::states_object, context::NO_FLAGS }; - constexpr static context state_context = {context::context_type::state, context::NO_FLAGS }; - constexpr static context rule_array = {context::context_type::rule_array, context::NO_FLAGS }; - constexpr static context rule_context = {context::context_type::rule, is_weighted ? context::REQUIRES_3 : context::REQUIRES_2 }; + assert(false); + return keys::unknown; + } + constexpr static context unknown_context = make_context(); + constexpr static context initial_context = make_context(); + constexpr static context pda_context = make_context(); + constexpr static context state_array = make_context(); + constexpr static context states_object = make_context(); + constexpr static context state_context = make_context(); + constexpr static context rule_array = make_context(); + constexpr static context rule_context = make_context(); - template + template // 'current_key' is the key to use. 'alternatives' are any other keys using the same flag in the same context (i.e. a one_of(current_key, alternatives...) requirement). bool handle_key() { static_assert(context::is_single_flag(flag), "Template parameter flag must be a single key, not a union or empty."); - static_assert(((context::get_key(type, flag) == current_key) || ... || (context::get_key(type, flag) == alternatives)), + static_assert(((get_key(type, flag) == current_key) || ... || (get_key(type, flag) == alternatives)), "The result of get_key(type, flag) must match 'key' or one of the alternatives"); - if (!context_stack.top().needs_value(flag)) { + if (!context_stack.top().needs_flag(flag)) { errors() << "Duplicate definition of key: \"" << current_key; ((errors() << "\"/\"" << alternatives), ...); errors() << "\" in " << type << " object. " << std::endl; return false; } - context_stack.top().got_value(flag); + context_stack.top().got_flag(flag); last_key = current_key; return true; } @@ -293,7 +282,7 @@ namespace pdaaal { break; } case keys::weight: - if constexpr (expect_weight) { // TODO: Parameterize on weight type... + if constexpr (expect_weight && !W::is_vector) { // TODO: Parameterize on weight type... if (value >= std::numeric_limits::max()) { errors() << "error: Unsigned value " << value << " is too large. Maximum value is: " << std::numeric_limits::max()-1 << std::endl; return false; @@ -368,10 +357,10 @@ namespace pdaaal { return true; } switch (context_stack.top().type) { - case context::context_type::state_array: + case context_type::state_array: context_stack.push(state_context); return true; - case context::context_type::rule_array: + case context_type::rule_array: context_stack.push(rule_context); return true; default: @@ -410,21 +399,21 @@ namespace pdaaal { return false; } switch (context_stack.top().type) { - case context::context_type::initial: + case context_type::initial: if (key == "pda") { - if (!handle_key()) return false; + if (!handle_key()) return false; } else { last_key = keys::unknown; } break; - case context::context_type::pda: + case context_type::pda: if (key == "states") { - if (!handle_key()) return false; + if (!handle_key()) return false; } else { // "additionalProperties": true last_key = keys::unknown; } break; - case context::context_type::states_object: + case context_type::states_object: if constexpr(use_state_names) { last_key = keys::state_name; current_from_state = build_pda.insert_state(key); @@ -433,7 +422,7 @@ namespace pdaaal { errors() << "error: Encountered state name: \"" << key << "\" in context: " << context_stack.top().type << ", but state names are disabled in this setting." << std::endl; return false; } - case context::context_type::state: + case context_type::state: last_key = keys::from_label; if (key == "*") { current_pre = std::vector{}; @@ -443,19 +432,19 @@ namespace pdaaal { current_wildcard = false; } break; - case context::context_type::rule: + case context_type::rule: if (key == "to") { - if (!handle_key()) return false; + if (!handle_key()) return false; } else if (key == "pop") { - if (!handle_key()) return false; + if (!handle_key()) return false; } else if (key == "swap") { - if (!handle_key()) return false; + if (!handle_key()) return false; } else if (key == "push") { - if (!handle_key()) return false; + if (!handle_key()) return false; } else { if constexpr (expect_weight) { if (key == "weight") { - if (!handle_key()) return false; + if (!handle_key()) return false; break; } } @@ -463,7 +452,7 @@ namespace pdaaal { return false; } break; - case context::context_type::unknown: + case context_type::unknown: break; default: errors() << "error: Encountered unexpected key: \"" << key << "\" in context: " << context_stack.top().type << std::endl; @@ -476,13 +465,13 @@ namespace pdaaal { errors() << "error: Unexpected end of object." << std::endl; return false; } - if (context_stack.top().missing_keys()) { + if (context_stack.top().has_missing_flags()) { errors() << "error: Missing key(s): "; bool first = true; for (const auto& flag : context_stack.top().get_missing_flags()) { if (!first) errors() << ", "; first = false; - auto key = context::get_key(context_stack.top().type, flag); + auto key = get_key(context_stack.top().type, flag); errors() << key; if (key == keys::pop) { errors() << "/" << keys::swap << "/" << keys::push; @@ -492,12 +481,12 @@ namespace pdaaal { return false; } switch (context_stack.top().type) { - case context::context_type::state: + case context_type::state: if constexpr (!use_state_names) { ++current_from_state; } break; - case context::context_type::rule: + case context_type::rule: build_pda.add_rule_detail(current_from_state, current_rule, current_wildcard, current_pre); break; default: @@ -505,7 +494,7 @@ namespace pdaaal { } context_stack.pop(); if constexpr(embedded_parser) { - if (context_stack.top().type == context::context_type::initial) { + if (context_stack.top().type == context_type::initial) { return false; // Stop using this SAXHandler. } } @@ -548,7 +537,7 @@ namespace pdaaal { } }; - class PdaJSONParser { + class PdaJsonParser { public: template , bool use_state_names = true> static auto parse(std::istream& stream, std::ostream& /*warnings*/, json::input_format_t format = json::input_format_t::json) { diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h index a7c2a84..750b7db 100644 --- a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h +++ b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h @@ -35,7 +35,7 @@ //using json = nlohmann::json; -namespace pdaaal { +namespace pdaaal::parsing { // We wish to be able to stop parsing before the end of input, but still handle errors correctly. // Keep track of whether the error stream was used, and this way distinguish between error and early return. @@ -60,33 +60,21 @@ namespace pdaaal { bool _errored = false; }; - template - class SAXHandlerContext : utils::flags { - using parent_t = utils::flags; - public: - using flag_t = typename parent_t::flag_t; + template + struct parser_object_context : public utils::flag_mask { + using type_t = context_type; // Expose template parameter. + using parent_t = utils::flag_mask; + using key_flag = typename parent_t::flag_t; using parent_t::flag; using parent_t::fill; using parent_t::is_single_flag; - - explicit constexpr SAXHandlerContext(flag_t flags) noexcept : values_left(flags) {}; - - flag_t values_left; - constexpr void got_value(flag_t value) { - parent_t::remove(values_left, value); - } - [[nodiscard]] constexpr bool needs_value(flag_t value) const { - return parent_t::contains(values_left, value); - } - [[nodiscard]] constexpr bool missing_keys() const { - return values_left != parent_t::no_flags; - } - std::vector get_missing_flags() const { - return parent_t::split_to_single_flags(values_left); - } - + constexpr parser_object_context(context_type type, key_flag flags) noexcept : parent_t(flags), type(type) {}; + const context_type type; }; - + template + static constexpr context make_object_context() { + return context(type, context::template fill()); + } } diff --git a/test/Parser_test.cpp b/test/Parser_test.cpp index 4707b3b..b587ebd 100644 --- a/test/Parser_test.cpp +++ b/test/Parser_test.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(PAutomatonFromJson_OldFormat_Test) {"accepting":true,"edges":[]} ] }})"); - auto automaton = PAutomatonJsonParser::parse<>(automaton_stream, pda); + auto automaton = parsing::PAutomatonJsonParser::parse<>(automaton_stream, pda); std::vector stack; stack.emplace_back(0); bool result = Solver::post_star_accepts(automaton, 0, stack); BOOST_CHECK(result); @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(PAutomatonFromJson_NewFormat_Test_Fails) [1,"A",2] ] }})"); - auto automaton = PAutomatonJsonParser::parse<>(automaton_stream, pda); + auto automaton = parsing::PAutomatonJsonParser::parse<>(automaton_stream, pda); std::vector stack; stack.emplace_back(0); bool result = Solver::post_star_accepts(automaton, 0, stack); BOOST_CHECK(result); diff --git a/test/Verification_test.cpp b/test/Verification_test.cpp index b75f048..528d6f0 100644 --- a/test/Verification_test.cpp +++ b/test/Verification_test.cpp @@ -33,6 +33,7 @@ #include using namespace pdaaal; +using namespace pdaaal::parsing; template void print_trace(const std::vector& trace, const pda_t& pda, std::ostream& s = std::cout) { @@ -119,7 +120,7 @@ BOOST_AUTO_TEST_CASE(Verification_Test_1) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto initial_p_automaton = PAutomatonParser::parse_string("< [Zero, One] , ([A]?[B])* >", pda); auto final_p_automaton = PAutomatonParser::parse_string("< [Two] , [B] [B] [B] >", pda); PAutomatonProduct instance(pda, std::move(initial_p_automaton), std::move(final_p_automaton)); @@ -150,7 +151,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_weight_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton = PAutomatonParser::parse_string("< [q] , >", pda); Solver::pre_star_fixed_point(p_automaton); @@ -181,7 +182,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_weight_loop_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton = PAutomatonParser::parse_string("< [p] , >", pda); Solver::pre_star_fixed_point(p_automaton); @@ -204,7 +205,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_weight_loop_path_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , .* >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -233,7 +234,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_weight_loop_path2_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , .+ >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -263,7 +264,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_weight_loop_not_accepting_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [q] , [Y] .+ >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -295,7 +296,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_weight_finite_path_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [a] , [X] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [c] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -327,7 +328,7 @@ BOOST_AUTO_TEST_CASE(Verification_poststar_pop_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X] [X] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , [X] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -354,7 +355,7 @@ BOOST_AUTO_TEST_CASE(Verification_poststar_empty_final_stack_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -381,7 +382,7 @@ BOOST_AUTO_TEST_CASE(Verification_poststar_no_ET_empty_final_stack_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -418,7 +419,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_ring_push_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X1] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , [X1] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -453,7 +454,7 @@ BOOST_AUTO_TEST_CASE(Verification_negative_ring_swap_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X1] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -488,7 +489,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X1] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -516,7 +517,7 @@ BOOST_AUTO_TEST_CASE(Incremental_Parsing_vs_Wildcard_test) } } })"); - auto pda = PdaJSONParser::parse(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p0], [B] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [p0], >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -545,7 +546,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_arithmetic_3_5_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [s] , [S] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [f] , [F] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -599,7 +600,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_arithmetic_3_3_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [s] , [S] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [f] , [F] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -633,7 +634,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_arithmetic_3_3or5_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [s] , [S] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [f] , [F] | ([F] [X] [X] [X] [X] [X] [X] [X] [X] [X] [X] [X] [X] [X] [X] [X]) >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -693,7 +694,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_which_pop_seq_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [s] , [S] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [f] , [F] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -743,7 +744,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_arithmetic_pop3loop_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [s] , [S] [X]* >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [f] , [F] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -775,7 +776,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_hill_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [p] , [X] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [q] , >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); @@ -830,7 +831,7 @@ BOOST_AUTO_TEST_CASE(Verification_longest_trace_start_hill_end_test) } } })"); - auto pda = PdaJSONParser::parse,true>(pda_stream, std::cerr); + auto pda = PdaJsonParser::parse,true>(pda_stream, std::cerr); auto p_automaton_i = PAutomatonParser::parse_string("< [s] , [S] >", pda); auto p_automaton_f = PAutomatonParser::parse_string("< [f] , [F] >", pda); PAutomatonProduct instance(pda, std::move(p_automaton_i), std::move(p_automaton_f)); From e49137789ed19a27a40c911080a33a96d435428a Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Fri, 25 Feb 2022 14:29:20 +0100 Subject: [PATCH 05/16] Generalize parser to SAXHandlerContextStack + improve error messages --- src/include/pdaaal/utils/flags.h | 2 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 216 +++++++++------------ src/pdaaal-bin/parsing/SaxHandlerHelpers.h | 147 ++++++++++++-- 3 files changed, 229 insertions(+), 136 deletions(-) diff --git a/src/include/pdaaal/utils/flags.h b/src/include/pdaaal/utils/flags.h index 8835897..060a83c 100644 --- a/src/include/pdaaal/utils/flags.h +++ b/src/include/pdaaal/utils/flags.h @@ -114,7 +114,7 @@ namespace pdaaal::utils { std::vector get_missing_flags() const { return flags::split_to_single_flags(_mask); } -// private: + private: flag_t _mask; }; diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index 1b814ec..b1ba9b9 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -41,19 +41,7 @@ using json = nlohmann::json; namespace pdaaal::parsing { - template , bool use_state_names = true, bool embedded_parser = false> - class PdaSaxHandler : public SAXHandlerBase { - public: - using pda_t = std::conditional_t, - PDA>; - private: - using build_pda_t = std::conditional_t, - PDA>; - - static constexpr bool expect_weight = is_weighted; - + struct PdaSaxHelper { enum class keys : uint32_t { none, unknown, pda, states, state_name, from_label, to, pop, swap, push, weight }; friend constexpr std::ostream& operator<<(std::ostream& s, keys key) { switch (key) { @@ -94,8 +82,8 @@ namespace pdaaal::parsing { return s; } enum class context_type : uint8_t { unknown, initial, pda, state_array, states_object, state, rule_array, rule }; - friend constexpr std::ostream& operator<<(std::ostream& s, context_type t) { - switch (t) { + friend constexpr std::ostream& operator<<(std::ostream& s, context_type type) { + switch (type) { case context_type::unknown: s << ""; break; @@ -123,16 +111,12 @@ namespace pdaaal::parsing { } return s; } - using context = parser_object_context; - using key_flag = typename context::key_flag; - static constexpr auto FLAG_1 = context::template flag<1>(); - static constexpr auto FLAG_2 = context::template flag<2>(); - static constexpr auto FLAG_3 = context::template flag<3>(); - template static constexpr context make_context() { - return make_object_context(); - } - static constexpr keys get_key(context_type context_type, key_flag flag) { - switch (context_type) { + static constexpr std::size_t N = 3; + static constexpr auto FLAG_1 = utils::flag_mask::template flag<1>(); + static constexpr auto FLAG_2 = utils::flag_mask::template flag<2>(); + static constexpr auto FLAG_3 = utils::flag_mask::template flag<3>(); + static constexpr keys get_key(context_type type, typename utils::flag_mask::flag_t flag) { + switch (type) { case context_type::initial: if (flag == FLAG_1) { return keys::pda; @@ -144,26 +128,15 @@ namespace pdaaal::parsing { } break; case context_type::rule: - if constexpr (expect_weight) { - switch (flag) { - case FLAG_1: - return keys::to; - case FLAG_2: - return keys::pop; // NOTE: Also keys::swap and keys::push - case FLAG_3: - return keys::weight; - default: - break; - } - } else { - switch (flag) { - case FLAG_1: - return keys::to; - case FLAG_2: - return keys::pop; // NOTE: Also keys::swap and keys::push - default: - break; - } + switch (flag) { + case FLAG_1: + return keys::to; + case FLAG_2: + return keys::pop; // NOTE: Also keys::swap and keys::push + case FLAG_3: + return keys::weight; + default: + break; } break; default: @@ -172,34 +145,30 @@ namespace pdaaal::parsing { assert(false); return keys::unknown; } - constexpr static context unknown_context = make_context(); - constexpr static context initial_context = make_context(); - constexpr static context pda_context = make_context(); - constexpr static context state_array = make_context(); - constexpr static context states_object = make_context(); - constexpr static context state_context = make_context(); - constexpr static context rule_array = make_context(); - constexpr static context rule_context = make_context(); + }; - template - // 'current_key' is the key to use. 'alternatives' are any other keys using the same flag in the same context (i.e. a one_of(current_key, alternatives...) requirement). - bool handle_key() { - static_assert(context::is_single_flag(flag), "Template parameter flag must be a single key, not a union or empty."); - static_assert(((get_key(type, flag) == current_key) || ... || (get_key(type, flag) == alternatives)), - "The result of get_key(type, flag) must match 'key' or one of the alternatives"); - if (!context_stack.top().needs_flag(flag)) { - errors() << "Duplicate definition of key: \"" << current_key; - ((errors() << "\"/\"" << alternatives), ...); - errors() << "\" in " << type << " object. " << std::endl; - return false; - } - context_stack.top().got_flag(flag); - last_key = current_key; - return true; - } + template , bool use_state_names = true, bool embedded_parser = false> + class PdaSaxHandler : public SAXHandlerContextStack { + using parent_t = SAXHandlerContextStack; + public: + using pda_t = std::conditional_t, + PDA>; + private: + using build_pda_t = std::conditional_t, + PDA>; + + static constexpr bool expect_weight = W::is_weight; - std::stack context_stack; - keys last_key = keys::none; + static constexpr context_t unknown_context = context_object(); + static constexpr context_t initial_context = context_object(); + static constexpr context_t pda_context = context_object(); + static constexpr context_t state_array = context_array(); + static constexpr context_t states_object = context_object(); + static constexpr context_t state_context = context_object(); + static constexpr context_t rule_array = context_array(); + static constexpr context_t rule_context = context_object(); build_pda_t build_pda; @@ -210,19 +179,14 @@ namespace pdaaal::parsing { void init() { if constexpr(embedded_parser) { - context_stack.push(initial_context); + push_context(initial_context); last_key = keys::pda; } } public: - using number_integer_t = typename json::number_integer_t; - using number_unsigned_t = typename json::number_unsigned_t; - using number_float_t = typename json::number_float_t; - using string_t = typename json::string_t; - using binary_t = typename json::binary_t; - explicit PdaSaxHandler(std::ostream& errors = std::cerr) : SAXHandlerBase(errors) { init(); }; - explicit PdaSaxHandler(const SAXHandlerBase& base) : SAXHandlerBase(base) { init(); }; + explicit PdaSaxHandler(std::ostream& errors = std::cerr) : parent_t(errors) { init(); }; + explicit PdaSaxHandler(const SAXHandlerBase& base) : parent_t(base) { init(); }; pda_t get_pda() { return pda_t(std::move(build_pda)); @@ -233,9 +197,10 @@ namespace pdaaal::parsing { case keys::unknown: break; default: - errors() << "error: Unexpected null value after key: " << last_key << std::endl; + error_unexpected("null value"); return false; } + element_done(); return true; } bool boolean(bool value) { @@ -243,9 +208,10 @@ namespace pdaaal::parsing { case keys::unknown: break; default: - errors() << "error: Unexpected boolean value: " << value << " after key: " << last_key << std::endl; + error_unexpected("boolean", value); return false; } + element_done(); return true; } bool number_integer(number_integer_t value) { @@ -266,9 +232,10 @@ namespace pdaaal::parsing { break; } default: - errors() << "error: Integer value: " << value << " found after key:" << last_key << std::endl; + error_unexpected("integer", value); return false; } + element_done(); return true; } bool number_unsigned(number_unsigned_t value) { @@ -293,9 +260,10 @@ namespace pdaaal::parsing { case keys::unknown: break; default: - errors() << "error: Unsigned value: " << value << " found after key: " << last_key << std::endl; + error_unexpected("unsigned", value); return false; } + element_done(); return true; } bool number_float(number_float_t value, const string_t& /*unused*/) { @@ -303,14 +271,15 @@ namespace pdaaal::parsing { case keys::unknown: break; default: - errors() << "error: Float value: " << value << " comes after key: " << last_key << std::endl; + error_unexpected("float", value); return false; } + element_done(); return true; } bool string(string_t& value) { - if (context_stack.empty()) { - errors() << "error: Unexpected string value: \"" << value << "\" outside of object." << std::endl; + if (no_context()) { + error_unexpected("string", value); return false; } switch (last_key) { @@ -339,66 +308,67 @@ namespace pdaaal::parsing { break; default: case keys::none: - errors() << "error: String value: " << value << " found after key:" << last_key << std::endl; + error_unexpected("string", value); return false; } return true; } bool binary(binary_t& /*val*/) { if (last_key == keys::unknown) { + element_done(); return true; } - errors() << "error: Unexpected binary value found after key:" << last_key << std::endl; + error_unexpected("binary value"); return false; } bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { - if (context_stack.empty()) { - context_stack.push(initial_context); + if (no_context()) { + push_context(initial_context); return true; } - switch (context_stack.top().type) { + switch (current_context_type()) { case context_type::state_array: - context_stack.push(state_context); + push_context(state_context); return true; case context_type::rule_array: - context_stack.push(rule_context); + push_context(rule_context); return true; default: break; } switch (last_key) { case keys::pda: - context_stack.push(pda_context); + push_context(pda_context); break; case keys::states: if constexpr(use_state_names) { - context_stack.push(states_object); + push_context(states_object); break; } else { errors() << "error: Found object after key: " << last_key << ", but state names are disabled in this setting. Try with --state-names." << std::endl; return false; } case keys::state_name: - context_stack.push(state_context); + push_context(state_context); break; case keys::from_label: - context_stack.push(rule_context); + push_context(rule_context); break; case keys::unknown: - context_stack.push(unknown_context); + push_context(unknown_context); break; default: - errors() << "error: Found object after key: " << last_key << std::endl; + error_unexpected("start of object"); return false; } return true; } bool key(string_t& key) { - if (context_stack.empty()) { + if (no_context()) { errors() << "Expected the start of an object before key: " << key << std::endl; return false; } - switch (context_stack.top().type) { + switch (current_context_type()) { case context_type::initial: if (key == "pda") { if (!handle_key()) return false; @@ -419,7 +389,7 @@ namespace pdaaal::parsing { current_from_state = build_pda.insert_state(key); break; } else { - errors() << "error: Encountered state name: \"" << key << "\" in context: " << context_stack.top().type << ", but state names are disabled in this setting." << std::endl; + errors() << "error: Encountered state name: \"" << key << "\" in context: " << current_context_type() << ", but state names are disabled in this setting." << std::endl; return false; } case context_type::state: @@ -455,32 +425,32 @@ namespace pdaaal::parsing { case context_type::unknown: break; default: - errors() << "error: Encountered unexpected key: \"" << key << "\" in context: " << context_stack.top().type << std::endl; + errors() << "error: Encountered unexpected key: \"" << key << "\" in context: " << current_context_type() << std::endl; return false; } return true; } bool end_object() { - if (context_stack.empty()) { + if (no_context()) { errors() << "error: Unexpected end of object." << std::endl; return false; } - if (context_stack.top().has_missing_flags()) { - errors() << "error: Missing key(s): "; + if (current_context().has_missing_flags()) { + auto& s = (errors() << "error: Missing key(s): "); bool first = true; - for (const auto& flag : context_stack.top().get_missing_flags()) { - if (!first) errors() << ", "; + for (const auto& flag : current_context().get_missing_flags()) { + if (!first) s << ", "; first = false; - auto key = get_key(context_stack.top().type, flag); - errors() << key; + auto key = get_key(current_context_type(), flag); + s << key; if (key == keys::pop) { - errors() << "/" << keys::swap << "/" << keys::push; + s << "/" << keys::swap << "/" << keys::push; } } - errors() << " in object: " << context_stack.top().type << std::endl; + s << " in object: " << current_context_type() << std::endl; return false; } - switch (context_stack.top().type) { + switch (current_context_type()) { case context_type::state: if constexpr (!use_state_names) { ++current_from_state; @@ -492,16 +462,17 @@ namespace pdaaal::parsing { default: break; } - context_stack.pop(); + pop_context(); if constexpr(embedded_parser) { - if (context_stack.top().type == context_type::initial) { + if (current_context_type() == context_type::initial) { return false; // Stop using this SAXHandler. } } + element_done(); return true; } bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { - if (context_stack.empty()) { + if (no_context()) { errors() << "error: Encountered start of array, but must start with an object." << std::endl; return false; } @@ -511,28 +482,29 @@ namespace pdaaal::parsing { errors() << "Unexpected start of array after key " << last_key << ". Note that state names are used in this setting." << std::endl; return false; } else { - context_stack.push(state_array); + push_context(state_array); current_from_state = 0; break; } case keys::from_label: - context_stack.push(rule_array); + push_context(rule_array); break; case keys::unknown: - context_stack.push(unknown_context); + push_context(unknown_context); break; default: - errors() << "Unexpected start of array after key " << last_key << std::endl; + error_unexpected("start of array"); return false; } return true; } bool end_array() { - if (context_stack.empty()) { + if (no_context()) { errors() << "error: Unexpected end of array." << std::endl; return false; } - context_stack.pop(); + pop_context(); + element_done(); return true; } }; diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h index 750b7db..1012849 100644 --- a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h +++ b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h @@ -31,9 +31,10 @@ #include #include #include +#include +#include #include - -//using json = nlohmann::json; +#include namespace pdaaal::parsing { @@ -60,21 +61,141 @@ namespace pdaaal::parsing { bool _errored = false; }; + // Object used when parsing JSON files with some required format. + // A context can either be an object (where we keep track of the required keys) + // or an array (where we keep track of the current index). Used by SAXHandlerContextStack. template - struct parser_object_context : public utils::flag_mask { + struct JsonParserContext { using type_t = context_type; // Expose template parameter. - using parent_t = utils::flag_mask; - using key_flag = typename parent_t::flag_t; - using parent_t::flag; - using parent_t::fill; - using parent_t::is_single_flag; - constexpr parser_object_context(context_type type, key_flag flags) noexcept : parent_t(flags), type(type) {}; + using mask_t = utils::flag_mask; + using flag_t = typename mask_t::flag_t; + using array_size_t = flag_t; + + template + constexpr JsonParserContext(context_type type, flag_t flags, std::in_place_index_t) noexcept + : type(type), _v(std::in_place_index, flags) {}; + + [[nodiscard]] constexpr bool is_object() const { return _v.index() == 0; } + [[nodiscard]] constexpr bool is_array() const { return _v.index() == 1; } + + constexpr void got_flag(flag_t value) { std::get<0>(_v).got_flag(value); } + [[nodiscard]] constexpr bool needs_flag(flag_t value) const { return std::get<0>(_v).needs_flag(value); } + [[nodiscard]] constexpr bool has_missing_flags() const { return std::get<0>(_v).has_missing_flags(); } + std::vector get_missing_flags() const { return std::get<0>(_v).get_missing_flags(); } + + [[nodiscard]] constexpr array_size_t get_index() const { return std::get<1>(_v); } + constexpr void increment_index() { ++std::get<1>(_v); } + const context_type type; + private: + std::variant _v; + }; + template inline constexpr context make_context_object() { + return context(type, context::mask_t::template fill(), std::in_place_index<0>); + }; + template inline constexpr context make_context_array() { + return context(type, 0, std::in_place_index<1>); + }; + + // This class provides functionality for SAXHandlers (JSON parsers) that need to keep track of a context stack, + // where contexts can have some required keys. Supports descriptive error messages. + // The template class should provide the following: + // - 'enum class keys' with printing 'ostream& operator<<(ostream&,keys)'. + // - 'enum class context_type' with printing 'ostream& operator<<(ostream&,context_type)'. + // - 'static constexpr size_t N', which is the largest number of keys needed. (N also determines to uint size for keeping track of array index). + // - 'static constexpr keys get_key(context_type,flag)' that for object contexts map the type and flag to the corresponding key (only needed if handle_key is used). + template + struct SAXHandlerContextStack : public SAXHandlerBase, public Helper { + using keys = typename Helper::keys; + using context_type = typename Helper::context_type; + static constexpr std::size_t N = Helper::N; + using context_t = JsonParserContext; + + using number_integer_t = typename nlohmann::json::number_integer_t; + using number_unsigned_t = typename nlohmann::json::number_unsigned_t; + using number_float_t = typename nlohmann::json::number_float_t; + using string_t = typename nlohmann::json::string_t; + using binary_t = typename nlohmann::json::binary_t; + + explicit SAXHandlerContextStack(std::ostream& errors) : SAXHandlerBase(errors) {}; + explicit SAXHandlerContextStack(const SAXHandlerBase& base) : SAXHandlerBase(base) {}; + + template static constexpr context_t context_object() { + return make_context_object(); + } + template static constexpr context_t context_array() { + return make_context_array(); + } + + void pop_context() { _context_stack.pop(); } + void push_context(const context_t& c) { _context_stack.push(c); } + [[nodiscard]] bool no_context() const { return _context_stack.empty(); } + [[nodiscard]] const context_t& current_context() const { return _context_stack.top(); } + [[nodiscard]] context_t& current_context() { return _context_stack.top(); } + [[nodiscard]] context_type current_context_type() const { return _context_stack.top().type; } + + template + bool handle_key() { + static_assert(context_t::mask_t::is_single_flag(flag), "Template parameter flag must be a single key, not a union or empty."); + static_assert(((Helper::get_key(type, flag) == current_key) || ... || (Helper::get_key(type, flag) == alternatives)), + "The result of get_key(type, flag) must match 'key' or one of the alternatives"); + assert(current_context().is_object()); + if (!current_context().needs_flag(flag)) { + auto& s = (errors() << "Duplicate definition of key: \"" << current_key); + ((s << "\"/\"" << alternatives), ...); + s << "\" in " << type << " object. " << std::endl; + return false; + } + current_context().got_flag(flag); + last_key = current_key; + return true; + } + + void element_done() { + if (no_context()) return; + if (current_context().is_array()) { + current_context().increment_index(); + } else { + last_key = keys::none; + } + } + + void error_unexpected(const std::string& what) { + auto& s = (errors() << "error: Unexpected " << what); + describe_context(s) << std::endl; + } + template void error_unexpected(const std::string& what, const value_t& value) { + auto& s = (errors() << "error: Unexpected " << what << " value "); + print_value(s, value); + describe_context(s) << "." << std::endl; + } + template static std::ostream& print_value(std::ostream& s, const value_t& value) { + if constexpr(std::is_same_v) { + s << "\"" << value << "\""; + } else if constexpr(std::is_same_v) { + s << std::boolalpha << value; + } else { + s << value; + } + return s; + } + std::ostream& describe_context(std::ostream& s) const { + if (no_context()) { + s << " outside of object"; + } else { + if (current_context().is_object()) { + s << " after key \"" << last_key << "\" in " << current_context_type(); + } else { + assert(current_context().is_array()); + s << " in " << current_context_type() << " at index " << current_context().get_index(); + } + } + return s; + } + keys last_key = keys::none; + private: + std::stack _context_stack; }; - template - static constexpr context make_object_context() { - return context(type, context::template fill()); - } } From 3e8fbc14e2a0cecf19da3f81f7809541d5833d5e Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Fri, 25 Feb 2022 17:50:47 +0100 Subject: [PATCH 06/16] simplify code + bugfix --- src/include/pdaaal/utils/flags.h | 2 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 116 ++++++--------------- src/pdaaal-bin/parsing/SaxHandlerHelpers.h | 63 +++++++++-- 3 files changed, 85 insertions(+), 96 deletions(-) diff --git a/src/include/pdaaal/utils/flags.h b/src/include/pdaaal/utils/flags.h index 060a83c..a408a2f 100644 --- a/src/include/pdaaal/utils/flags.h +++ b/src/include/pdaaal/utils/flags.h @@ -84,7 +84,7 @@ namespace pdaaal::utils { std::vector flags; for (std::size_t i = 0; i < N; ++i) { flag_t f = (flag_t)1<()) return false; - } else { + return handle_key(); + } else { // "additionalProperties": true last_key = keys::unknown; } break; case context_type::pda: if (key == "states") { - if (!handle_key()) return false; + return handle_key(); } else { // "additionalProperties": true last_key = keys::unknown; } @@ -404,29 +368,26 @@ namespace pdaaal::parsing { break; case context_type::rule: if (key == "to") { - if (!handle_key()) return false; + return handle_key(); } else if (key == "pop") { - if (!handle_key()) return false; + return handle_key(); } else if (key == "swap") { - if (!handle_key()) return false; + return handle_key(); } else if (key == "push") { - if (!handle_key()) return false; + return handle_key(); } else { if constexpr (expect_weight) { if (key == "weight") { - if (!handle_key()) return false; - break; + return handle_key(); } } - errors() << "Unexpected key in operation object: " << key << std::endl; - return false; + return error_unexpected_key(key); } break; case context_type::unknown: break; default: - errors() << "error: Encountered unexpected key: \"" << key << "\" in context: " << current_context_type() << std::endl; - return false; + return error_unexpected_key(key); } return true; } @@ -436,19 +397,7 @@ namespace pdaaal::parsing { return false; } if (current_context().has_missing_flags()) { - auto& s = (errors() << "error: Missing key(s): "); - bool first = true; - for (const auto& flag : current_context().get_missing_flags()) { - if (!first) s << ", "; - first = false; - auto key = get_key(current_context_type(), flag); - s << key; - if (key == keys::pop) { - s << "/" << keys::swap << "/" << keys::push; - } - } - s << " in object: " << current_context_type() << std::endl; - return false; + return error_missing_keys(); } switch (current_context_type()) { case context_type::state: @@ -468,8 +417,7 @@ namespace pdaaal::parsing { return false; // Stop using this SAXHandler. } } - element_done(); - return true; + return element_done(); } bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { if (no_context()) { @@ -493,8 +441,7 @@ namespace pdaaal::parsing { push_context(unknown_context); break; default: - error_unexpected("start of array"); - return false; + return error_unexpected("start of array"); } return true; } @@ -504,8 +451,7 @@ namespace pdaaal::parsing { return false; } pop_context(); - element_done(); - return true; + return element_done(); } }; @@ -513,12 +459,12 @@ namespace pdaaal::parsing { public: template , bool use_state_names = true> static auto parse(std::istream& stream, std::ostream& /*warnings*/, json::input_format_t format = json::input_format_t::json) { - std::stringstream es; // For errors; - PdaSaxHandler my_sax(es); - if (!json::sax_parse(stream, &my_sax, format)) { - throw std::runtime_error(es.str()); + std::stringstream error_stream; + PdaSaxHandler pda_sax(error_stream); + if (!json::sax_parse(stream, &pda_sax, format)) { + throw std::runtime_error(error_stream.str()); } - return my_sax.get_pda(); + return pda_sax.get_pda(); } }; } diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h index 1012849..264a0d1 100644 --- a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h +++ b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h @@ -141,9 +141,8 @@ namespace pdaaal::parsing { "The result of get_key(type, flag) must match 'key' or one of the alternatives"); assert(current_context().is_object()); if (!current_context().needs_flag(flag)) { - auto& s = (errors() << "Duplicate definition of key: \"" << current_key); - ((s << "\"/\"" << alternatives), ...); - s << "\" in " << type << " object. " << std::endl; + auto& s = (errors() << "Duplicate definition of key: "); + print_keys(s) << " in " << type << " object." << std::endl; return false; } current_context().got_flag(flag); @@ -151,23 +150,60 @@ namespace pdaaal::parsing { return true; } - void element_done() { - if (no_context()) return; - if (current_context().is_array()) { + bool element_done() { + if (!no_context() && current_context().is_array()) { current_context().increment_index(); } else { last_key = keys::none; } + return true; // Allows for nice code at the use site. } - void error_unexpected(const std::string& what) { + bool error_unexpected(const std::string& what) { auto& s = (errors() << "error: Unexpected " << what); - describe_context(s) << std::endl; + describe_context(s) << "." << std::endl; + return false; // Allows for nice code at the use site. } - template void error_unexpected(const std::string& what, const value_t& value) { + template bool error_unexpected(const std::string& what, const value_t& value, const std::string& explanation = "") { auto& s = (errors() << "error: Unexpected " << what << " value "); print_value(s, value); - describe_context(s) << "." << std::endl; + describe_context(s) << "."; + if (!explanation.empty()) { + s << " " << explanation; + } + s << std::endl; + return false; // Allows for nice code at the use site. + } + bool error_unexpected_key(const string_t& key) { + auto& s = (errors() << "error: Unexpected key "); + print_value(s, key); + if (no_context()) { + s << " before the start of an object." << std::endl; + } else { + s << " in " << current_context_type() << " object." << std::endl; + } + return false; // Allows for nice code at the use site. + } + template // If one out of multiple alternative keys are required, we can specify it here. (only supports one such set) + bool error_missing_keys() { + auto& s = (errors() << "error: Missing key(s): "); + bool first = true; + for (const auto& flag : current_context().get_missing_flags()) { + if (!first) s << ", "; + first = false; + if constexpr (sizeof...(alternatives) == 0) { + s << "\"" << Helper::get_key(current_context_type(), flag) << "\""; + } else { + auto key = Helper::get_key(current_context_type(), flag); + if (((key == alternatives) || ...)) { + print_keys(s); + } else { + s << "\"" << key << "\""; + } + } + } + s << " in " << current_context_type() << " object." << std::endl; + return false; } template static std::ostream& print_value(std::ostream& s, const value_t& value) { if constexpr(std::is_same_v) { @@ -194,6 +230,13 @@ namespace pdaaal::parsing { } keys last_key = keys::none; private: + template + static constexpr std::ostream& print_keys(std::ostream& s) { + s << "\"" << key; + ((s << "\"/\"" << alternatives), ...); + return s << "\""; + } + std::stack _context_stack; }; From d29caea0650914af840c8cb04be1a4de1a9de8a6 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Fri, 25 Feb 2022 22:59:48 +0100 Subject: [PATCH 07/16] Add new PAutomatonJsonParser --- src/include/pdaaal/PAutomaton.h | 13 +- src/include/pdaaal/PDA.h | 2 + src/include/pdaaal/internal/PAutomaton.h | 7 + src/pdaaal-bin/parsing/PAutomatonJsonParser.h | 369 ++++++++++++++++++ test/Parser_test.cpp | 4 +- 5 files changed, 392 insertions(+), 3 deletions(-) diff --git a/src/include/pdaaal/PAutomaton.h b/src/include/pdaaal/PAutomaton.h index e408f49..1794437 100644 --- a/src/include/pdaaal/PAutomaton.h +++ b/src/include/pdaaal/PAutomaton.h @@ -87,7 +87,7 @@ namespace pdaaal { } size_t insert_state(const state_t& state) { if constexpr (skip_state_mapping) { - return state; // + this->pda().states().size(); + return state; } else { return this->_state_map.insert(state).second + _pda.states().size(); } @@ -122,6 +122,17 @@ namespace pdaaal { template PAutomaton(const PDA&, const std::vector&, bool special_accepting = true) -> PAutomaton; + // Simplify type deduction elsewhere. + namespace details { + template struct pda_to_pautomaton; + template + struct pda_to_pautomaton, trace_info_type> { + using type = PAutomaton; + }; + } + template + using pda_to_pautomaton_t = typename details::pda_to_pautomaton::type; + template void to_json(json& j, const PAutomaton& automaton) { j = json::object(); diff --git a/src/include/pdaaal/PDA.h b/src/include/pdaaal/PDA.h index cb85d7b..68454f2 100644 --- a/src/include/pdaaal/PDA.h +++ b/src/include/pdaaal/PDA.h @@ -61,6 +61,8 @@ namespace pdaaal { class PDA : public internal::PDA, public std::conditional_t> { public: using parent_t = internal::PDA; + using expose_state_t = state_t; // Expose template parameter as dependent name. + static constexpr bool expose_skip_state_mapping = skip_state_mapping; protected: using impl_rule_t = typename internal::PDA::rule_t; // This rule type is used internally. static_assert(!skip_state_mapping || std::is_same_v, "When skip_state_mapping==true, you must use state_t=size_t"); diff --git a/src/include/pdaaal/internal/PAutomaton.h b/src/include/pdaaal/internal/PAutomaton.h index 466d9ef..f5d627e 100644 --- a/src/include/pdaaal/internal/PAutomaton.h +++ b/src/include/pdaaal/internal/PAutomaton.h @@ -628,6 +628,13 @@ namespace pdaaal::internal { } return id; } + void set_state_accepting(size_t id) { + assert(id < _states.size()); + if (!_states[id]->_accepting) { + _states[id]->_accepting = true; + _accepting.push_back(_states[id].get()); + } + } [[nodiscard]] size_t next_state_id() const { return _states.size(); } diff --git a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h index 0e77fe0..c1f61c2 100644 --- a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h +++ b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h @@ -27,10 +27,379 @@ #ifndef PDAAAL_PAUTOMATONJSONPARSER_H #define PDAAAL_PAUTOMATONJSONPARSER_H +#include "SaxHandlerHelpers.h" #include namespace pdaaal::parsing { + struct PAutomatonSaxHelper { + enum class keys : uint32_t { none, p_automaton, initial, accepting, edges }; + friend constexpr std::ostream& operator<<(std::ostream& s, keys key) { + switch (key) { + case keys::none: + s << ""; + break; + case keys::p_automaton: + s << "P-automaton"; + break; + case keys::initial: + s << "initial"; + break; + case keys::accepting: + s << "accepting"; + break; + case keys::edges: + s << "edges"; + break; + } + return s; + } + enum class context_type : uint8_t { initial, p_automaton, initial_array, accepting_array, edges_array, edge_context }; + friend constexpr std::ostream& operator<<(std::ostream& s, context_type t) { + switch (t) { + case context_type::initial: + s << ""; + break; + case context_type::p_automaton: + s << "P-automaton"; + break; + case context_type::initial_array: + s << "initial array"; + break; + case context_type::accepting_array: + s << "accepting array"; + break; + case context_type::edges_array: + s << "edges array"; + break; + case context_type::edge_context: + s << "edge"; + break; + } + return s; + } + static constexpr std::size_t N = 3; + static constexpr auto FLAG_1 = utils::flag_mask::template flag<1>(); + static constexpr auto FLAG_2 = utils::flag_mask::template flag<2>(); + static constexpr auto FLAG_3 = utils::flag_mask::template flag<3>(); + static constexpr keys get_key(context_type type, typename utils::flag_mask::flag_t flag) { + switch (type) { + case context_type::initial: + if (flag == FLAG_1) { + return keys::p_automaton; + } + break; + case context_type::p_automaton: + switch (flag) { + case FLAG_1: + return keys::initial; + case FLAG_2: + return keys::accepting; + case FLAG_3: + return keys::edges; + default: + break; + } + break; + default: + break; + } + assert(false); + return keys::none; + } + }; + + template + class PAutomatonSaxHandler : public SAXHandlerContextStack { + using parent_t = SAXHandlerContextStack; + + using automaton_t = pda_to_pautomaton_t; + + using pda_state_t = typename pda_t::expose_state_t; + static constexpr bool skip_state_mapping = pda_t::expose_skip_state_mapping; + + constexpr static context_t initial_context = context_object(); + constexpr static context_t p_automaton = context_object(); // TODO: Consider making "initial" optional (since it can be derived from the PDA). + constexpr static context_t initial_array = context_array(); + constexpr static context_t accepting_array = context_array(); + constexpr static context_t edges_array = context_array(); + constexpr static context_t edge_context = context_array(); + + pda_t& pda; + automaton_t automaton; + + std::unordered_map extra_state_map; + + std::string current_label; + size_t current_from_state = 0; + size_t current_to_state = 0; + + void init() { + if constexpr(embedded_parser) { + push_context(initial_context); + last_key = keys::p_automaton; + } + } + + size_t number_state(number_unsigned_t state) { + if constexpr(std::is_same_v) { + auto [exists, id] = automaton.exists_state(state); + if (exists) return id; + if constexpr(!skip_state_mapping) { + id = automaton.add_state(false, false); // accepting is set later. +#ifndef NDEBUG + auto id2 = +#endif + automaton.insert_state(state); + assert(id == id2); + return id; + } + } + if constexpr(skip_state_mapping || !std::is_same_v){ + // This is a non-initial state, without a name that is recorded, so we use an auxiliary mapping. + auto it = extra_state_map.find(state); + if (it != extra_state_map.end()) { + return it->second; + } + auto id = automaton.add_state(false, false); // accepting is set later. + extra_state_map.emplace(state,id); + return id; + } + } + std::optional string_state(const string_t& state) { + if constexpr(std::is_same_v) { + auto [exists, id] = automaton.exists_state(state); + if (!exists) { + id = automaton.add_state(false, false); // accepting is set later. +#ifndef NDEBUG + auto id2 = +#endif + automaton.insert_state(state); + assert(id == id2); + } + return id; + } else { + error_unexpected("string", state, "PDA does not use string states."); + return std::nullopt; + } + } + + public: + + explicit PAutomatonSaxHandler(pda_t& pda, std::ostream& errors = std::cerr) : parent_t(errors), pda(pda), automaton(pda,std::vector()) { init(); }; + PAutomatonSaxHandler(pda_t& pda, const SAXHandlerBase& base) : parent_t(base), pda(pda), automaton(pda,std::vector()) { init(); }; + + automaton_t get_automaton() { + return std::move(automaton); + } + + bool null() { return error_unexpected("null value"); } + bool boolean(bool value) { return error_unexpected("boolean", value); } + bool number_integer(number_integer_t value) { return error_unexpected("integer", value); } + bool number_unsigned(number_unsigned_t value) { + switch (current_context_type()) { + case context_type::initial_array: + if constexpr(std::is_same_v) { + if (!pda.exists_state(value).first) { + errors() << "Initial state " << value << " is not in PDA." << std::endl; + return false; + } + } else { + return error_unexpected("unsigned", value, "PDA does not use indexed states."); + } + break; + case context_type::accepting_array: + automaton.set_state_accepting(number_state(value)); + break; + case context_type::edge_context: + switch (current_context().get_index()) { + case 0: + current_from_state = number_state(value); + break; + case 1: + return error_unexpected("unsigned", value); + case 2: + current_to_state = number_state(value); + break; + default: + return error_unexpected("unsigned", value, "An edge should contain exactly three elements."); + } + break; + default: + return error_unexpected("unsigned", value); + } + return element_done(); + } + bool number_float(number_float_t value, const string_t& /*unused*/) { + return error_unexpected("float", value); + } + bool string(string_t& value) { + if (no_context()) { + return error_unexpected("string", value); + } + switch (current_context_type()) { + case context_type::initial_array: + if constexpr(std::is_same_v) { + if (!pda.exists_state(value).first) { + errors() << "Initial state \"" << value << "\" is not in PDA." << std::endl; + return false; + } + } else { + return error_unexpected("string", value, "PDA does not use string states."); + } + break; + case context_type::accepting_array: { + if (auto s = string_state(value); !s) return false; else { + automaton.set_state_accepting(s.value()); + } + break; + } + case context_type::edge_context: + switch (current_context().get_index()) { + case 0: + if (auto s = string_state(value); !s) return false; else { + current_from_state = s.value(); + } + break; + case 1: + current_label = value; + break; + case 2: + if (auto s = string_state(value); !s) return false; else { + current_to_state = s.value(); + } + break; + default: + return error_unexpected("string", value, "An edge should contain exactly three elements."); + } + break; + default: + return error_unexpected("string", value); + } + return element_done(); + } + bool binary(binary_t& /*val*/) { + return error_unexpected("binary value"); + } + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { + if (no_context()) { + push_context(initial_context); + return true; + } + if (last_key == keys::p_automaton) { + push_context(p_automaton); + return true; + } + return error_unexpected("start of object"); + } + bool key(string_t& key) { + if (no_context()) { + return error_unexpected_key(key); + } + switch (current_context_type()) { + case context_type::initial: + if (key == "P-automaton") { + if (!handle_key()) return false; + } else { + return error_unexpected_key(key); + } + break; + case context_type::p_automaton: + if (key == "initial") { + if (!handle_key()) return false; + } else if (key == "accepting") { + if (!handle_key()) return false; + } else if (key == "edges"){ + if (!handle_key()) return false; + } else { + return error_unexpected_key(key); + } + break; + default: + return error_unexpected_key(key); + } + return true; + } + bool end_object() { + if (no_context()) { + errors() << "error: Unexpected end of object." << std::endl; + return false; + } + if (current_context().has_missing_flags()) { + return error_missing_keys(); + } + pop_context(); + if constexpr(embedded_parser) { + if (current_context_type() == context_type::initial) { + return false; // Stop using this SAXHandler. + } + } + return element_done(); + } + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { + if (no_context()) { + errors() << "error: Encountered start of array, but must start with an object." << std::endl; + return false; + } + if (current_context_type() == context_type::edges_array) { + push_context(edge_context); + current_from_state = std::numeric_limits::max(); + current_to_state = std::numeric_limits::max(); + current_label.clear(); + return true; + } + switch (last_key) { + case keys::initial: + push_context(initial_array); + break; + case keys::accepting: + push_context(accepting_array); + break; + case keys::edges: + push_context(edges_array); + break; + default: + error_unexpected("start of array"); + return false; + } + return true; + } + bool end_array() { + if (no_context()) { + errors() << "error: Unexpected end of array." << std::endl; + return false; + } + switch (current_context_type()) { + case context_type::edge_context: + assert(current_from_state != std::numeric_limits::max()); + assert(current_to_state != std::numeric_limits::max()); + if (current_label.empty()) { + automaton.add_epsilon_edge(current_from_state, current_to_state); + } else { + automaton.add_edge(current_from_state, current_to_state, pda.insert_label(current_label)); + } + break; + default: + break; + } + pop_context(); + return element_done(); + } + }; + + class PAutomatonJsonParser_New { + public: + template + static auto parse(std::istream& stream, pda_t& pda, json::input_format_t format = json::input_format_t::json) { + std::stringstream error_stream; + PAutomatonSaxHandler automaton_sax(pda, error_stream); + if (!json::sax_parse(stream, &automaton_sax, format)) { + throw std::runtime_error(error_stream.str()); + } + return automaton_sax.get_automaton(); + } + }; + class PAutomatonJsonParser { public: template diff --git a/test/Parser_test.cpp b/test/Parser_test.cpp index b587ebd..9eadaef 100644 --- a/test/Parser_test.cpp +++ b/test/Parser_test.cpp @@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(PAutomatonFromJson_OldFormat_Test) BOOST_CHECK(result); } -BOOST_AUTO_TEST_CASE(PAutomatonFromJson_NewFormat_Test_Fails) +BOOST_AUTO_TEST_CASE(PAutomatonFromJson_NewFormat_Test) { std::unordered_set labels{"A"}; PDA pda(labels); @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(PAutomatonFromJson_NewFormat_Test_Fails) [1,"A",2] ] }})"); - auto automaton = parsing::PAutomatonJsonParser::parse<>(automaton_stream, pda); + auto automaton = parsing::PAutomatonJsonParser_New::parse<>(automaton_stream, pda); std::vector stack; stack.emplace_back(0); bool result = Solver::post_star_accepts(automaton, 0, stack); BOOST_CHECK(result); From 941ccebd6e2dd2235daa3691fed1606e95deccdd Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Fri, 25 Feb 2022 23:19:38 +0100 Subject: [PATCH 08/16] Add SolverInstanceJsonParser --- src/include/pdaaal/PAutomatonProduct.h | 2 +- src/pdaaal-bin/main.cpp | 18 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 233 ++++++++++++++++ src/pdaaal-bin/parsing/SaxHandlerHelpers.h | 91 +++++++ .../parsing/SolverInstanceJsonParser.h | 256 ++++++++++++++++++ 5 files changed, 598 insertions(+), 2 deletions(-) create mode 100644 src/pdaaal-bin/parsing/SolverInstanceJsonParser.h diff --git a/src/include/pdaaal/PAutomatonProduct.h b/src/include/pdaaal/PAutomatonProduct.h index 4daf19d..bb69d6d 100644 --- a/src/include/pdaaal/PAutomatonProduct.h +++ b/src/include/pdaaal/PAutomatonProduct.h @@ -153,7 +153,7 @@ namespace pdaaal { [[nodiscard]] nlohmann::json to_json() const { nlohmann::json j; - j = *this; + j["instance"] = *this; return j; } diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index 46a234f..ec80714 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -24,6 +24,7 @@ * Created on 11-06-2021. */ +#include "parsing/SolverInstanceJsonParser.h" #include "parsing/Parsing.h" #include "Verifier.h" @@ -48,12 +49,13 @@ int main(int argc, const char** argv) { bool no_parser_warnings = false; bool silent = false; bool print_pda_json = false; - std::string solver_instance_file; + std::string solver_instance_file, solver_instance_input; output.add_options() ("disable-parser-warnings,W", po::bool_switch(&no_parser_warnings), "Disable warnings from parser.") ("silent,s", po::bool_switch(&silent), "Disables non-essential output (implies -W).") ("print-pda-json", po::bool_switch(&print_pda_json), "Print PDA in JSON format to terminal.") // TODO: This is currently mostly a debug option. Make it useful! ("print-solver-instance", po::value(&solver_instance_file), "Print SolverInstance in JSON format to file.") + ("input-solver-instance", po::value(&solver_instance_input), "Print SolverInstance in JSON format to file.") // TODO: Move option to Input Options... ; opts.add(parsing.options()); opts.add(verifier.options()); @@ -77,6 +79,20 @@ int main(int argc, const char** argv) { } if (silent) { no_parser_warnings = true; } + if (!solver_instance_input.empty()) { + std::ifstream input_stream(solver_instance_input); + if (!input_stream.is_open()) { + std::stringstream es; + es << "error: Could not open file: " << solver_instance_input << std::endl; + throw std::runtime_error(es.str()); + } + std::stringstream dummy; + auto solver_instance = parsing::SolverInstanceJsonParser::parse<>(input_stream, no_parser_warnings ? dummy : std::cerr); + std::visit([](auto&& solver_instance){ + std::cout << solver_instance->to_json().dump() << std::endl; + }, solver_instance); + return 0; + } auto pda_variant = parsing.parse(no_parser_warnings); if (!silent) { diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index 4c72308..b340f9d 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -467,6 +467,239 @@ namespace pdaaal::parsing { return pda_sax.get_pda(); } }; + + + struct PdaTypeSaxHelper { + enum class keys : uint32_t { none, state_names, weight_type }; + friend constexpr std::ostream& operator<<(std::ostream& s, keys key) { + switch (key) { + case keys::none: + s << ""; + break; + case keys::state_names: + s << "state-names"; + break; + case keys::weight_type: + s << "weight-type"; + break; + } + return s; + } + enum class context_type : uint8_t { initial, weight_array }; + friend constexpr std::ostream& operator<<(std::ostream& s, context_type type) { + switch (type) { + case context_type::initial: + s << ""; + break; + case context_type::weight_array: + s << "weight-type array"; + break; + } + return s; + } + static constexpr std::size_t N = 2; + static constexpr auto FLAG_1 = utils::flag_mask::template flag<1>(); + static constexpr auto FLAG_2 = utils::flag_mask::template flag<2>(); + static constexpr keys get_key(context_type type, typename utils::flag_mask::flag_t flag) { + switch (type) { + case context_type::initial: + if (flag == FLAG_1) { + return keys::state_names; + } + if (flag == FLAG_2) { + return keys::weight_type; + } + break; + default: + break; + } + assert(false); + return keys::none; + } + }; + + class PdaTypeSaxHandler : public SAXHandlerContextStack { + using parent_t = SAXHandlerContextStack; + + constexpr static context_t initial_context = context_object(); + constexpr static context_t weight_array_context = context_array(); + + template + bool handle_index(const value_t& value) { + if (current_context().get_index() != index) { + auto& s = (errors() << "error: value "); + print_value(s, value) << "at index " << current_context().get_index() << " in " << type << " was expected to be at index " << index << "." << std::endl; + return false; + } + current_context().increment_index(); + return true; + } + + template struct weight_to_pda_sax_handler; + template struct weight_to_pda_sax_handler> { + using type = std::variant, true, true>..., + PdaSaxHandler, false, true>...>; + }; + template using weight_to_pda_sax_handler_t = typename weight_to_pda_sax_handler::type; + template struct get_pda; + template struct get_pda> { + using type = std::variant().get_pda())...>; + }; + template using get_pda_t = typename get_pda::type; + + using weight_types = std::tuple,std::vector>; + public: + using pda_sax_variant_t = weight_to_pda_sax_handler_t; + using pda_variant_t = get_pda_t; + private: + template + pda_sax_variant_t get_pda_sax_handler_w() { + if (use_state_names) { + return PdaSaxHandler(static_cast(*this)); + } else { + return PdaSaxHandler(static_cast(*this)); + } + } + template + pda_sax_variant_t get_pda_sax_handler_a() { + if (use_weight_array) { + if (weight_array_size) { + switch (weight_array_size.value()) { // TODO: Should we just ignore fixed size std::array to limit the amount of different types the compiler needs to deal with.? +// case 2: +// return get_pda_sax_handler_w>>(); +// case 3: +// return get_pda_sax_handler_w>>(); +// case 4: +// return get_pda_sax_handler_w>>(); + default: + return get_pda_sax_handler_w>>(); + } + } else { + return get_pda_sax_handler_w>>(); + } + } else { + return get_pda_sax_handler_w>(); + } + } + + std::string weight_str; + bool use_state_names = false; + bool use_weight_array = false; + std::optional weight_array_size; + + public: + explicit PdaTypeSaxHandler(std::ostream& errors) : parent_t(errors) {}; + explicit PdaTypeSaxHandler(const SAXHandlerBase& base) : parent_t(base) {}; + + pda_sax_variant_t get_pda_sax_handler() { + if (weight_str == "UINT") { + return get_pda_sax_handler_a(); + } else if (weight_str == "INT") { + return get_pda_sax_handler_a(); + } else { + return get_pda_sax_handler_w>(); + } + } + + bool null() { + return error_unexpected("null value"); + } + bool boolean(bool value) { + if (last_key == keys::state_names) { + use_state_names = value; + return element_done(); + } else { + return error_unexpected("boolean", value); + } + } + bool number_integer(number_integer_t value) { + return error_unexpected("integer", value); + } + bool number_unsigned(number_unsigned_t value) { + if (current_context_type() == context_type::weight_array) { + if (!handle_index(value)) return false; + weight_array_size.emplace(value); + return element_done(); + } + return error_unexpected("unsigned", value); + } + bool number_float(number_float_t value, const string_t& /*unused*/) { + return error_unexpected("float", value); + } + bool string(string_t& value) { + if (no_context()) { + return error_unexpected("string", value); + } + if (current_context_type() == context_type::weight_array) { + if (!handle_index(value)) return false; + weight_str = value; + return element_done(); + } + if (last_key == keys::weight_type) { + weight_str = value; + return element_done(); + } + return error_unexpected("string", value); + } + bool binary(binary_t& /*val*/) { + return error_unexpected("binary value"); + } + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { + if (no_context()) { + push_context(initial_context); + return true; + } + return error_unexpected("start of object"); + } + bool key(string_t& key) { + if (no_context()) { + return error_unexpected_key(key); + } + if (current_context_type() == context_type::initial) { + if (key == "state-names") { + return handle_key(); + } else if (key == "weight-type") { + return handle_key(); + } + } + return error_unexpected_key(key); + } + bool end_object() { + if (no_context()) { + errors() << "error: Unexpected end of object." << std::endl; + return false; + } + if (current_context().has_missing_flags()) { + return error_missing_keys(); + } + pop_context(); + if (no_context()) { + return false; // Stop using this SAXHandler. + } + return element_done(); + } + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { + if (no_context()) { + errors() << "error: Encountered start of array, but must start with an object." << std::endl; + return false; + } + if (last_key == keys::weight_type) { + push_context(weight_array_context); + use_weight_array = true; + return true; + } + return error_unexpected("start of array"); + } + bool end_array() { + if (no_context()) { + errors() << "error: Unexpected end of array." << std::endl; + return false; + } + pop_context(); + return element_done(); + } + }; + } #endif //PDAAAL_PDAJSONPARSER_H diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h index 264a0d1..385dfe7 100644 --- a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h +++ b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h @@ -240,6 +240,97 @@ namespace pdaaal::parsing { std::stack _context_stack; }; + class SAXHandlerDispatch { + public: + using number_integer_t = typename nlohmann::json::number_integer_t; + using number_unsigned_t = typename nlohmann::json::number_unsigned_t; + using number_float_t = typename nlohmann::json::number_float_t; + using string_t = typename nlohmann::json::string_t; + using binary_t = typename nlohmann::json::binary_t; + + template + SAXHandlerDispatch(other_handler_t& other_handler, CallbackFn&& callback) : _callback(std::forward(callback)) { + static_assert(std::is_base_of_v); + set_dispatch(other_handler); + } + template + constexpr void set_dispatch(other_handler_t& other_handler) { + static_assert(std::is_base_of_v); + _dispatch_null = [&](){ return other_handler.null(); }; + _dispatch_boolean = [&](bool value){ return other_handler.boolean(value); }; + _dispatch_number_integer = [&](number_integer_t value){ return other_handler.number_integer(value); }; + _dispatch_number_unsigned = [&](number_unsigned_t value){ return other_handler.number_unsigned(value); }; + _dispatch_number_float = [&](number_float_t value, const string_t& unused){ return other_handler.number_float(value,unused); }; + _dispatch_string = [&](string_t& value){ return other_handler.string(value); }; + _dispatch_binary = [&](binary_t& value){ return other_handler.binary(value); }; + _dispatch_key = [&](string_t& key){ return other_handler.key(key); }; + _dispatch_start_object = [&](std::size_t size){ return other_handler.start_object(size); }; + _dispatch_end_object = [&](){ return other_handler.end_object(); }; + _dispatch_start_array = [&](std::size_t size){ return other_handler.start_array(size); }; + _dispatch_end_array = [&](){ return other_handler.end_array(); }; + _dispatch_parse_error = [&](std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e){ return other_handler.parse_error(location, last_token, e); }; + _dispatch_is_errored = [&](){ return other_handler.is_errored(); }; + } + + bool null() { + return _dispatch_null() || (!_dispatch_is_errored() && _callback(*this)); + } + bool boolean(bool value) { + return _dispatch_boolean(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool number_integer(number_integer_t value) { + return _dispatch_number_integer(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool number_unsigned(number_unsigned_t value) { + return _dispatch_number_unsigned(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool number_float(number_float_t value, const string_t& unused) { + return _dispatch_number_float(value,unused) || (!_dispatch_is_errored() && _callback(*this)); + } + bool string(string_t& value) { + return _dispatch_string(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool binary(binary_t& value) { + return _dispatch_binary(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool key(string_t& key) { + return _dispatch_key(key) || (!_dispatch_is_errored() && _callback(*this)); + } + bool start_object(std::size_t size = std::size_t(-1)) { + return _dispatch_start_object(size) || (!_dispatch_is_errored() && _callback(*this)); + } + bool end_object() { + return _dispatch_end_object() || (!_dispatch_is_errored() && _callback(*this)); + } + bool start_array(std::size_t size = std::size_t(-1)) { + return _dispatch_start_array(size) || (!_dispatch_is_errored() && _callback(*this)); + } + bool end_array() { + return _dispatch_end_array() || (!_dispatch_is_errored() && _callback(*this)); + } + bool parse_error(std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e) { + return _dispatch_parse_error(location,last_token,e) || (!_dispatch_is_errored() && _callback(*this)); + } + + private: + std::function _callback; + + std::function _dispatch_null; + std::function _dispatch_boolean; + std::function _dispatch_number_integer; + std::function _dispatch_number_unsigned; + std::function _dispatch_number_float; + std::function _dispatch_string; + std::function _dispatch_binary; + std::function _dispatch_key; + std::function _dispatch_start_object; + std::function _dispatch_end_object; + std::function _dispatch_start_array; + std::function _dispatch_end_array; + std::function _dispatch_is_errored; + std::function _dispatch_parse_error; + }; + } #endif //PDAAAL_SAXHANDLERHELPERS_H diff --git a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h new file mode 100644 index 0000000..8c127e4 --- /dev/null +++ b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h @@ -0,0 +1,256 @@ +/* + * 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 . + */ + +/* + * Copyright Morten K. Schou + */ + +/* + * File: SolverInstanceJsonParser.h + * Author: Morten K. Schou + * + * Created on 23-02-2022. + */ + +#ifndef PDAAAL_SOLVERINSTANCEJSONPARSER_H +#define PDAAAL_SOLVERINSTANCEJSONPARSER_H + +#include "PdaJsonParser.h" +#include "PAutomatonJsonParser.h" +#include +#include + +using json = nlohmann::json; + +namespace pdaaal::parsing { + + namespace detail { + // Get index of a type in a variant. Only works if the variant has unique types. + // Based on https://stackoverflow.com/questions/52303316/get-index-by-type-in-stdvariant + // and https://stackoverflow.com/questions/53651609/why-does-this-get-index-implementation-fail-on-vs2017 + template struct tag { }; + template constexpr std::size_t variant_index() { return std::variant...>(tag()).index(); } + template struct get_index; + template + struct get_index> : std::integral_constant()> { }; + template constexpr size_t get_index_v = get_index::value; + } + + struct SolverInstanceSaxHelper { + enum class keys : uint32_t { none, instance }; + friend constexpr std::ostream& operator<<(std::ostream& s, keys key) { + switch (key) { + case keys::none: + s << ""; + break; + case keys::instance: + s << "instance"; + break; + } + return s; + } + enum class context_type : uint8_t { initial, instance }; + friend constexpr std::ostream& operator<<(std::ostream& s, context_type t) { + switch (t) { + case context_type::initial: + s << ""; + break; + case context_type::instance: + s << "instance array"; + break; + } + return s; + } + static constexpr std::size_t N = 1; + static constexpr auto FLAG_1 = utils::flag_mask::template flag<1>(); + static constexpr keys get_key(context_type type, typename utils::flag_mask::flag_t flag) { + if (type == context_type::initial && flag == FLAG_1) { + return keys::instance; + } + assert(false); + return keys::none; + } + }; + + template + class SolverInstanceSaxHandler : public SAXHandlerContextStack { + using parent_t = SAXHandlerContextStack; + + constexpr static context_t initial_context = context_object(); + constexpr static context_t instance_context = context_array(); + + using pda_sax_variant_t = PdaTypeSaxHandler::pda_sax_variant_t; + using pda_variant_t = PdaTypeSaxHandler::pda_variant_t; + + template struct pda_to_automaton_sax; + template struct pda_to_automaton_sax> { + using type = std::variant...>; + }; + template using pda_to_automaton_sax_t = typename pda_to_automaton_sax::type; + template struct get_automaton; + template struct get_automaton> { + using type = std::variant().get_automaton())...>; + }; + template using get_automaton_t = typename get_automaton::type; + template struct deduce_solver_instance; + template struct deduce_solver_instance> { + using type = std::variant(), + std::declval>().get_automaton(), + std::declval>().get_automaton()))...>; + }; + + using automaton_sax_variant_t = typename pda_to_automaton_sax::type; + using automaton_variant_t = typename get_automaton::type; + using solver_instance_variant_t = typename deduce_solver_instance::type; + + PdaTypeSaxHandler pda_type_sax_handler; + std::optional pda_sax_handler; + std::optional pda_variant; + std::optional automaton_sax_handler_1; + std::optional automaton_sax_handler_2; + + public: + using number_integer_t = typename nlohmann::json::number_integer_t; + using number_unsigned_t = typename nlohmann::json::number_unsigned_t; + using number_float_t = typename nlohmann::json::number_float_t; + using string_t = typename nlohmann::json::string_t; + using binary_t = typename nlohmann::json::binary_t; + + explicit SolverInstanceSaxHandler(std::ostream& errors) : parent_t(errors), pda_type_sax_handler(*this) {}; + explicit SolverInstanceSaxHandler(const SAXHandlerBase& base) : parent_t(base), pda_type_sax_handler(*this) {}; + + solver_instance_variant_t get_solver_instance() { + assert(pda_variant); + assert(automaton_sax_handler_1); + assert(automaton_sax_handler_2); + // Use std::visit to find type of pda_variant, and on compile-time find corresponding index + // and use it to std::get value of automaton_sax_handler_1 and automaton_sax_handler_2. + return std::visit([this](auto&& pda) { + using pda_t = std::decay_t; + constexpr auto i = detail::get_index_v; + auto& sax_1 = std::get(automaton_sax_handler_1.value()); + auto& sax_2 = std::get(automaton_sax_handler_2.value()); + return solver_instance_variant_t(SolverInstance(std::forward(pda), sax_1.get_automaton(), sax_2.get_automaton())); + }, std::move(pda_variant).value()); + } + + bool callback(SAXHandlerDispatch& dispatch_handler) { + switch (current_context().get_index()) { + case 0: + dispatch_handler.set_dispatch(pda_type_sax_handler); + break; + case 1: + pda_sax_handler.emplace(pda_type_sax_handler.get_pda_sax_handler()); + std::visit([&dispatch_handler](auto&& sax_handler){ dispatch_handler.set_dispatch(sax_handler); }, pda_sax_handler.value()); + break; + case 2: + pda_variant.emplace(std::visit([](auto&& sax_handler){ return pda_variant_t(sax_handler.get_pda()); }, pda_sax_handler.value())); + automaton_sax_handler_1.emplace(std::visit([this](auto&& pda){ + return automaton_sax_variant_t(PAutomatonSaxHandler,trace_info_type,true>(pda,*this)); + }, pda_variant.value())); + std::visit([&dispatch_handler](auto&& sax_handler){ dispatch_handler.set_dispatch(sax_handler); }, automaton_sax_handler_1.value()); + break; + case 3: + automaton_sax_handler_2.emplace(std::visit([this](auto&& pda){ + return automaton_sax_variant_t(PAutomatonSaxHandler,trace_info_type,true>(pda,*this)); + }, pda_variant.value())); + std::visit([&dispatch_handler](auto&& sax_handler){ dispatch_handler.set_dispatch(sax_handler); }, automaton_sax_handler_2.value()); + break; + case 4: + dispatch_handler.set_dispatch(*this); + break; + default: + errors() << "error: Solver instance should only contain four elements." << std::endl; + return false; + } + return element_done(); + } + + bool null() { return error_unexpected("null value"); } + bool boolean(bool value) { return error_unexpected("boolean", value); } + bool number_integer(number_integer_t value) { return error_unexpected("integer", value); } + bool number_unsigned(number_unsigned_t value) { return error_unexpected("unsigned", value); } + bool number_float(number_float_t value, const string_t& /*unused*/) { return error_unexpected("float", value); } + bool string(string_t& value) { return error_unexpected("string", value); } + bool binary(binary_t& /*val*/) { return error_unexpected("binary value"); } + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { + if (no_context()) { + push_context(initial_context); + return true; + } + return error_unexpected("start of object"); + } + bool key(string_t& key) { + if (no_context()) { + return error_unexpected_key(key); + } + if (current_context_type() == context_type::initial) { + if (key == "instance") { + return handle_key(); + } + } + return error_unexpected_key(key); + } + bool end_object() { + if (no_context()) { + errors() << "error: Unexpected end of object." << std::endl; + return false; + } + if (current_context().has_missing_flags()) { + return error_missing_keys(); + } + pop_context(); + return element_done(); + } + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { + if (no_context()) { + errors() << "error: Encountered start of array, but must start with an object." << std::endl; + return false; + } + if (last_key == keys::instance) { + push_context(instance_context); + return false; // Trigger callback. + } + return error_unexpected("start of array"); + } + bool end_array() { + if (no_context()) { + errors() << "error: Unexpected end of array." << std::endl; + return false; + } + pop_context(); + return element_done(); + } + + }; + + class SolverInstanceJsonParser { + public: + template + static auto parse(std::istream& stream, std::ostream& /*warnings*/, nlohmann::json::input_format_t format = nlohmann::json::input_format_t::json) { + std::stringstream error_stream; + SolverInstanceSaxHandler solver_instance_sax(error_stream); + SAXHandlerDispatch my_sax(solver_instance_sax, [&solver_instance_sax](SAXHandlerDispatch& dispatch_handler){ + return solver_instance_sax.callback(dispatch_handler); + }); + if (!json::sax_parse(stream, &my_sax, format)) { + throw std::runtime_error(error_stream.str()); + } + return solver_instance_sax.get_solver_instance(); + } + }; +} + +#endif //PDAAAL_SOLVERINSTANCEJSONPARSER_H From 13e1e7638f914360e81e60d65a0a98e054ba9344 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Mon, 28 Feb 2022 12:23:23 +0100 Subject: [PATCH 09/16] Fix move constructor of SolverInstance --- src/include/pdaaal/PAutomaton.h | 3 ++- src/include/pdaaal/PAutomatonProduct.h | 5 +++++ src/include/pdaaal/SolverInstance.h | 10 ++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/include/pdaaal/PAutomaton.h b/src/include/pdaaal/PAutomaton.h index 1794437..4feaede 100644 --- a/src/include/pdaaal/PAutomaton.h +++ b/src/include/pdaaal/PAutomaton.h @@ -39,6 +39,7 @@ namespace pdaaal { class PAutomaton : public internal::PAutomaton, private std::conditional_t> { static_assert(!skip_state_mapping || std::is_same_v, "When skip_state_mapping==true, you must use state_t=size_t"); using parent_t = internal::PAutomaton; + using parent2_t = std::conditional_t>; using pda_t = PDA; using internal_pda_t = typename pda_t::parent_t; public: @@ -61,7 +62,7 @@ namespace pdaaal { : parent_t(static_cast(pda), special_initial_states, special_accepting), _pda(pda) { }; PAutomaton(PAutomaton&& other, const pda_t& pda) noexcept // Move constructor, but update reference to PDA. - : parent_t(std::move(other), static_cast(pda)), _pda(pda) {}; + : parent_t(std::move(other), static_cast(pda)), parent2_t(std::move(other)), _pda(pda) {}; [[nodiscard]] nlohmann::json to_json(const std::string& name = "P-automaton") const { nlohmann::json j; diff --git a/src/include/pdaaal/PAutomatonProduct.h b/src/include/pdaaal/PAutomatonProduct.h index bb69d6d..a80eaeb 100644 --- a/src/include/pdaaal/PAutomatonProduct.h +++ b/src/include/pdaaal/PAutomatonProduct.h @@ -51,6 +51,11 @@ namespace pdaaal { _initial(std::move(initial), _pda), _final(std::move(final), _pda), _product(_pda, get_initial_accepting(_initial, _final), true) {}; + PAutomatonProduct(PAutomatonProduct&& other, const pda_t& pda) noexcept // Move constructor, but update reference to PDA. + : _pda(pda), _pda_size(_pda.states().size()), _initial(std::move(other._initial), _pda), _final(std::move(other._final), _pda), + _product(std::move(other._product), _pda), _swap_initial_final(other._swap_initial_final), _id_map(std::move(other._id_map)), + _id_fast_lookup(std::move(other._id_fast_lookup)), _id_fast_lookup_back(std::move(other._id_fast_lookup_back)) {}; + // Returns whether an accepting state in the product automaton was reached. template bool initialize_product() { diff --git a/src/include/pdaaal/SolverInstance.h b/src/include/pdaaal/SolverInstance.h index 9e0ba38..7e93bf0 100644 --- a/src/include/pdaaal/SolverInstance.h +++ b/src/include/pdaaal/SolverInstance.h @@ -50,6 +50,16 @@ namespace pdaaal { SolverInstance(pda_t&& pda, pautomaton_t&& initial, pautomaton_t&& final) : _pda(std::move(pda)), _product(_pda, std::move(initial), std::move(final)) {}; + SolverInstance(SolverInstance&& other) noexcept + : _pda(std::move(other._pda)), _product(std::move(other._product), _pda) {}; + SolverInstance& operator=(SolverInstance&& other) noexcept { + if (this != &other) { + _pda = std::move(other._pda); + _product = product_t(std::move(other._product), _pda); + } + return *this; + } + product_t* operator->() { return &_product; } From 4e837884c73a4fd77f9fe73e03bb0042280fa3eb Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Mon, 28 Feb 2022 14:20:30 +0100 Subject: [PATCH 10/16] small stuff --- src/pdaaal-bin/main.cpp | 14 +++++++------- src/pdaaal-bin/parsing/SaxHandlerHelpers.h | 18 +++++++++++++----- .../parsing/SolverInstanceJsonParser.h | 7 +++---- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index ec80714..de0762a 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -49,12 +49,12 @@ int main(int argc, const char** argv) { bool no_parser_warnings = false; bool silent = false; bool print_pda_json = false; - std::string solver_instance_file, solver_instance_input; + std::string solver_instance_output, solver_instance_input; output.add_options() ("disable-parser-warnings,W", po::bool_switch(&no_parser_warnings), "Disable warnings from parser.") ("silent,s", po::bool_switch(&silent), "Disables non-essential output (implies -W).") ("print-pda-json", po::bool_switch(&print_pda_json), "Print PDA in JSON format to terminal.") // TODO: This is currently mostly a debug option. Make it useful! - ("print-solver-instance", po::value(&solver_instance_file), "Print SolverInstance in JSON format to file.") + ("print-solver-instance", po::value(&solver_instance_output), "Print SolverInstance in JSON format to file.") ("input-solver-instance", po::value(&solver_instance_input), "Print SolverInstance in JSON format to file.") // TODO: Move option to Input Options... ; opts.add(parsing.options()); @@ -86,8 +86,8 @@ int main(int argc, const char** argv) { es << "error: Could not open file: " << solver_instance_input << std::endl; throw std::runtime_error(es.str()); } - std::stringstream dummy; - auto solver_instance = parsing::SolverInstanceJsonParser::parse<>(input_stream, no_parser_warnings ? dummy : std::cerr); + auto solver_instance = parsing::SolverInstanceJsonParser::parse<>(input_stream); + // TODO: Make Verifier work with this solver_instance. (for now we just print it to console) std::visit([](auto&& solver_instance){ std::cout << solver_instance->to_json().dump() << std::endl; }, solver_instance); @@ -104,15 +104,15 @@ int main(int argc, const char** argv) { }, pda_variant); return 0; // TODO: What else.? } - if (!solver_instance_file.empty()) { - std::ofstream out(solver_instance_file); + if (!solver_instance_output.empty()) { + std::ofstream out(solver_instance_output); if (out.is_open()) { std::visit([&out,&verifier](auto&& pda){ auto instance = verifier.get_product(std::forward(pda)); out << instance.to_json().dump() << std::endl; }, pda_variant); } else { - std::cerr << "Could not open --print-solver-instance\"" << solver_instance_file << "\" for writing" << std::endl; + std::cerr << "Could not open --print-solver-instance\"" << solver_instance_output << "\" for writing" << std::endl; exit(-1); } return 0; diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h index 385dfe7..de01996 100644 --- a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h +++ b/src/pdaaal-bin/parsing/SaxHandlerHelpers.h @@ -69,11 +69,12 @@ namespace pdaaal::parsing { using type_t = context_type; // Expose template parameter. using mask_t = utils::flag_mask; using flag_t = typename mask_t::flag_t; - using array_size_t = flag_t; + using array_size_t = size_t; - template - constexpr JsonParserContext(context_type type, flag_t flags, std::in_place_index_t) noexcept - : type(type), _v(std::in_place_index, flags) {}; + constexpr JsonParserContext(context_type type, flag_t flags, std::in_place_index_t<0>) noexcept + : type(type), _v(std::in_place_index<0>, flags) {}; + constexpr JsonParserContext(context_type type, array_size_t flags, std::in_place_index_t<1>) noexcept + : type(type), _v(std::in_place_index<1>, flags) {}; [[nodiscard]] constexpr bool is_object() const { return _v.index() == 0; } [[nodiscard]] constexpr bool is_array() const { return _v.index() == 1; } @@ -240,6 +241,12 @@ namespace pdaaal::parsing { std::stack _context_stack; }; + // This class allows combining multiple SAX handlers by providing SAXHandlerDispatch to nlohmann::json::sax_parse, + // and using the _callback function to change the SAX handler dispatch functions during parsing. + // The _callback function is called, when the internal SAX handler function returns false, but has not used the error stream. + // This indirect approach is not the most efficient, but nlohmann/json does not support combining SAX handlers + // (at least not in the public interface), so this is good enough for now. + // An alternative could be to use a different JSON library, but the maturity of nlohmann/json is currently preferred. class SAXHandlerDispatch { public: using number_integer_t = typename nlohmann::json::number_integer_t; @@ -250,7 +257,8 @@ namespace pdaaal::parsing { template SAXHandlerDispatch(other_handler_t& other_handler, CallbackFn&& callback) : _callback(std::forward(callback)) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of_v, + "The SAX handler needs to derive from SAXHandlerBase to allow checking if an error occurred when returning false."); set_dispatch(other_handler); } template diff --git a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h index 8c127e4..97e40bb 100644 --- a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h +++ b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h @@ -43,9 +43,8 @@ namespace pdaaal::parsing { template struct tag { }; template constexpr std::size_t variant_index() { return std::variant...>(tag()).index(); } template struct get_index; - template - struct get_index> : std::integral_constant()> { }; - template constexpr size_t get_index_v = get_index::value; + template struct get_index> : std::integral_constant()> { }; + template constexpr std::size_t get_index_v = get_index::value; } struct SolverInstanceSaxHelper { @@ -239,7 +238,7 @@ namespace pdaaal::parsing { class SolverInstanceJsonParser { public: template - static auto parse(std::istream& stream, std::ostream& /*warnings*/, nlohmann::json::input_format_t format = nlohmann::json::input_format_t::json) { + static auto parse(std::istream& stream, nlohmann::json::input_format_t format = nlohmann::json::input_format_t::json) { std::stringstream error_stream; SolverInstanceSaxHandler solver_instance_sax(error_stream); SAXHandlerDispatch my_sax(solver_instance_sax, [&solver_instance_sax](SAXHandlerDispatch& dispatch_handler){ From de2b9cb0442f5b08de9189ae798ca402e6975db1 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Mon, 28 Feb 2022 19:29:13 +0100 Subject: [PATCH 11/16] Use SolverInstance as the primary structure in Parser, Verifier and main --- src/include/pdaaal/Weight.h | 27 ++ src/pdaaal-bin/Verifier.h | 253 ++++++++---------- src/pdaaal-bin/main.cpp | 112 ++++---- src/pdaaal-bin/parsing/Parsing.cpp | 6 +- src/pdaaal-bin/parsing/Parsing.h | 60 ++++- .../parsing/SolverInstanceJsonParser.h | 10 +- 6 files changed, 258 insertions(+), 210 deletions(-) diff --git a/src/include/pdaaal/Weight.h b/src/include/pdaaal/Weight.h index f2f19c2..668dfd6 100644 --- a/src/include/pdaaal/Weight.h +++ b/src/include/pdaaal/Weight.h @@ -35,6 +35,7 @@ #include #include #include +#include namespace pdaaal { namespace details { @@ -54,6 +55,10 @@ namespace pdaaal { static constexpr bool is_signed = std::numeric_limits::is_signed; static constexpr bool is_vector = false; static constexpr auto zero = []() -> type { return static_cast(0); }; + static constexpr std::ostream& print(std::ostream& s, const type& w) { + s << w; + return s; + } }; template struct weight, std::enable_if_t && std::numeric_limits::is_specialized>> { @@ -66,6 +71,17 @@ namespace pdaaal { arr.fill(weight::zero()); return arr; }; + static constexpr std::ostream& print(std::ostream& s, const type& w) { + bool first = true; + s << "["; + for (const auto& elem : w) { + if (!first) s << ","; + first = false; + s << elem; + } + s << "]"; + return s; + } }; template struct weight, std::enable_if_t && std::numeric_limits::is_specialized>> { @@ -74,6 +90,17 @@ namespace pdaaal { static constexpr bool is_signed = weight::is_signed; static constexpr bool is_vector = true; static constexpr auto zero = []() -> type { return type{}; }; // TODO: When C++20 arrives, use a constexpr vector instead + static constexpr std::ostream& print(std::ostream& s, const type& w) { + bool first = true; + s << "["; + for (const auto& elem : w) { + if (!first) s << ","; + first = false; + s << elem; + } + s << "]"; + return s; + } }; namespace details { diff --git a/src/pdaaal-bin/Verifier.h b/src/pdaaal-bin/Verifier.h index 06522e9..4c2aa9a 100644 --- a/src/pdaaal-bin/Verifier.h +++ b/src/pdaaal-bin/Verifier.h @@ -78,164 +78,145 @@ namespace pdaaal { verification_options.add_options() ("engine,e", po::value(&engine), "Engine. 0=no verification, 1=post*, 2=pre*, 3=dual*") ("trace,t", po::value(&trace_type)->default_value(Trace_Type::None), "Trace type. 0=no trace, 1=any trace, 2=shortest trace, 3=longest trace, 4=fixed-point shortest trace") - ("initial-automaton,i", po::value(&initial_pa_file), "Initial PAutomaton file input.") - ("final-automaton,f", po::value(&final_pa_file), "Final PAutomaton file input.") - ("json-automata", po::bool_switch(&json_automata), "Parse Pautomata files using JSON format.") ; } [[nodiscard]] const po::options_description& options() const { return verification_options; } - template - auto get_product(PDA_T& pda) { - auto initial_p_automaton = json_automata ? - parsing::PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : - parsing::PAutomatonParser::parse_file(initial_pa_file, pda); - auto final_p_automaton = json_automata ? - parsing::PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : - parsing::PAutomatonParser::parse_file(final_pa_file, pda); - return PAutomatonProduct(pda, std::move(initial_p_automaton), std::move(final_p_automaton)); + [[nodiscard]] bool needs_trace_info_pair() const { + return trace_type == Trace_Type::Longest || trace_type == Trace_Type::ShortestFixedPoint; } - template - void verify(PDA_T& pda) { - using pda_t = std20::remove_cvref_t; + template + void verify(instance_t& instance) { + using pda_t = std20::remove_cvref_t; bool result = false; std::vector trace; - switch (trace_type) { - case Trace_Type::None: - case Trace_Type::Any: - case Trace_Type::Shortest: { - auto instance = get_product(pda); - switch (engine) { - case 1: { - std::cout << "Using post*" << std::endl; - switch (trace_type) { - case Trace_Type::None: - result = Solver::post_star_accepts(instance); - break; - case Trace_Type::Any: - result = Solver::post_star_accepts(instance); + if constexpr (trace_info_type == TraceInfoType::Single) { + switch (engine) { + case 1: { + std::cout << "Using post*" << std::endl; + switch (trace_type) { + case Trace_Type::None: + result = Solver::post_star_accepts(instance); + break; + case Trace_Type::Any: + result = Solver::post_star_accepts(instance); + if (result) { + trace = Solver::get_trace(instance); + } + break; + case Trace_Type::Shortest: + if constexpr(pda_t::has_weight) { + result = Solver::post_star_accepts(instance); if (result) { - trace = Solver::get_trace(instance); - } - break; - case Trace_Type::Shortest: - if constexpr(pda_t::has_weight) { - result = Solver::post_star_accepts(instance); - if (result) { - typename pda_t::weight_type weight; - std::tie(trace, weight) = Solver::get_trace(instance); - std::cout << "Weight: " << weight << std::endl; - } - } else { - assert(false); - throw std::runtime_error("Cannot use shortest trace option for unweighted PDA."); + typename pda_t::weight_type weight; + using W = typename pda_t::weight; + std::tie(trace, weight) = Solver::get_trace(instance); + std::cout << "Weight: "; W::print(std::cout, weight) << std::endl; } - break; - case Trace_Type::Longest: - case Trace_Type::ShortestFixedPoint: + } else { assert(false); - throw std::logic_error("Impossible control flow in switches"); - break; - } - break; + throw std::runtime_error("Cannot use shortest trace option for unweighted PDA."); + } + break; + case Trace_Type::Longest: + case Trace_Type::ShortestFixedPoint: + assert(false); + throw std::logic_error("Impossible control flow in switches"); + break; } - case 2: { - std::cout << "Using pre*" << std::endl; - switch (trace_type) { - case Trace_Type::None: - result = Solver::pre_star_accepts(instance); - break; - case Trace_Type::Any: - result = Solver::pre_star_accepts(instance); - if (result) { - trace = Solver::get_trace(instance); - } - break; - case Trace_Type::Shortest: - assert(false); - throw std::runtime_error("Cannot use shortest trace, not implemented for pre* engine."); - break; - case Trace_Type::Longest: - case Trace_Type::ShortestFixedPoint: - assert(false); - throw std::logic_error("Impossible control flow in switches"); - break; - } - break; + break; + } + case 2: { + std::cout << "Using pre*" << std::endl; + switch (trace_type) { + case Trace_Type::None: + result = Solver::pre_star_accepts(instance); + break; + case Trace_Type::Any: + result = Solver::pre_star_accepts(instance); + if (result) { + trace = Solver::get_trace(instance); + } + break; + case Trace_Type::Shortest: + assert(false); + throw std::runtime_error("Cannot use shortest trace, not implemented for pre* engine."); + break; + case Trace_Type::Longest: + case Trace_Type::ShortestFixedPoint: + assert(false); + throw std::logic_error("Impossible control flow in switches"); + break; } - case 3: { - std::cout << "Using dual*" << std::endl; - switch (trace_type) { - case Trace_Type::None: - result = Solver::dual_search_accepts(instance); - break; - case Trace_Type::Any: - result = Solver::dual_search_accepts(instance); - if (result) { - trace = Solver::get_trace_dual_search(instance); - } - break; - case Trace_Type::Shortest: - assert(false); - throw std::runtime_error("Cannot use shortest trace, not implemented for dual* engine."); - break; - case Trace_Type::Longest: - case Trace_Type::ShortestFixedPoint: - assert(false); - throw std::logic_error("Impossible control flow in switches"); - break; - } - break; + break; + } + case 3: { + std::cout << "Using dual*" << std::endl; + switch (trace_type) { + case Trace_Type::None: + result = Solver::dual_search_accepts(instance); + break; + case Trace_Type::Any: + result = Solver::dual_search_accepts(instance); + if (result) { + trace = Solver::get_trace_dual_search(instance); + } + break; + case Trace_Type::Shortest: + assert(false); + throw std::runtime_error("Cannot use shortest trace, not implemented for dual* engine."); + break; + case Trace_Type::Longest: + case Trace_Type::ShortestFixedPoint: + assert(false); + throw std::logic_error("Impossible control flow in switches"); + break; } + break; } - break; } - case Trace_Type::Longest: - case Trace_Type::ShortestFixedPoint: { - auto instance = get_product(pda); - switch (engine) { - case 1: - case 3: { - assert(false); - throw std::runtime_error("Cannot use fixed-point (longest or shortest) trace, not implemented for post* and dual* engine."); - } - case 2: { - std::cout << "Using pre*" << std::endl; - if constexpr(pda_t::has_weight) { - if (trace_type == Trace_Type::Longest) { - result = Solver::pre_star_fixed_point_accepts(instance); - if (result) { - typename pda_t::weight_type weight; - std::tie(trace, weight) = Solver::get_trace(instance); - using W = typename pda_t::weight; - if (weight == internal::solver_weight::bottom()) { - std::cout << "Weight: infinity" << std::endl; - } else { - std::cout << "Weight: " << weight << std::endl; - } + } else { + switch (engine) { + case 1: + case 3: { + assert(false); + throw std::runtime_error("Cannot use fixed-point (longest or shortest) trace, not implemented for post* and dual* engine."); + } + case 2: { + std::cout << "Using pre*" << std::endl; + if constexpr(pda_t::has_weight) { + if (trace_type == Trace_Type::Longest) { + result = Solver::pre_star_fixed_point_accepts(instance); + if (result) { + typename pda_t::weight_type weight; + std::tie(trace, weight) = Solver::get_trace(instance); + using W = typename pda_t::weight; + if (weight == internal::solver_weight::bottom()) { + std::cout << "Weight: infinity" << std::endl; + } else { + std::cout << "Weight: "; W::print(std::cout, weight) << std::endl; } - } else { // (trace_type == Trace_Type::ShortestFixedPoint) - result = Solver::pre_star_fixed_point_accepts(instance); - if (result) { - typename pda_t::weight_type weight; - std::tie(trace, weight) = Solver::get_trace(instance); - using W = typename pda_t::weight; - if (weight == internal::solver_weight::bottom()) { - std::cout << "Weight: negative infinity" << std::endl; - } else { - std::cout << "Weight: " << weight << std::endl; - } + } + } else { // (trace_type == Trace_Type::ShortestFixedPoint) + result = Solver::pre_star_fixed_point_accepts(instance); + if (result) { + typename pda_t::weight_type weight; + std::tie(trace, weight) = Solver::get_trace(instance); + using W = typename pda_t::weight; + if (weight == internal::solver_weight::bottom()) { + std::cout << "Weight: negative infinity" << std::endl; + } else { + std::cout << "Weight: "; W::print(std::cout, weight) << std::endl; } } - } else { - assert(false); - throw std::runtime_error("Cannot use fixed-point (longest or shortest) trace option for unweighted PDA."); } + } else { + assert(false); + throw std::runtime_error("Cannot use fixed-point (longest or shortest) trace option for unweighted PDA."); } } - break; } } @@ -260,8 +241,6 @@ namespace pdaaal { po::options_description verification_options; size_t engine = 0; Trace_Type trace_type = Trace_Type::None; - std::string initial_pa_file, final_pa_file; - bool json_automata = false; //bool print_trace = false; }; } diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index de0762a..001962a 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -36,6 +36,44 @@ namespace po = boost::program_options; using namespace pdaaal; +class main_output { +public: + main_output(const std::string& caption = "Output Options") : output_options(caption) { + output_options.add_options() + ("silent,s", po::bool_switch(&silent), "Disables non-essential output.") + ("print-pda-json", po::value(&pda_json_output), "Print PDA in JSON format to terminal.") + ("print-solver-instance", po::value(&solver_instance_output), "Print SolverInstance in JSON format to file.") + ; + } + [[nodiscard]] const po::options_description& options() const { return output_options; } + + template + void do_output(instance_t&& instance) { + if (!pda_json_output.empty()) { + std::ofstream out(pda_json_output); + if (!out.is_open()) { + std::cerr << "Could not open --print-pda-json \"" << pda_json_output << "\" for writing" << std::endl; + exit(-1); + } + out << instance->pda().to_json().dump() << std::endl; + } + if (!solver_instance_output.empty()) { + std::ofstream out(solver_instance_output); + if (!out.is_open()) { + std::cerr << "Could not open --print-solver-instance \"" << solver_instance_output << "\" for writing" << std::endl; + exit(-1); + } + out << instance->to_json().dump() << std::endl; + } + } + + bool silent = false; +private: + po::options_description output_options; + std::string solver_instance_output, pda_json_output; +}; + + int main(int argc, const char** argv) { po::options_description opts; opts.add_options() @@ -44,22 +82,11 @@ int main(int argc, const char** argv) { parsing::Parsing parsing("Input Options"); Verifier verifier("Verification Options"); - po::options_description output("Output Options"); + main_output output("Output Options"); - bool no_parser_warnings = false; - bool silent = false; - bool print_pda_json = false; - std::string solver_instance_output, solver_instance_input; - output.add_options() - ("disable-parser-warnings,W", po::bool_switch(&no_parser_warnings), "Disable warnings from parser.") - ("silent,s", po::bool_switch(&silent), "Disables non-essential output (implies -W).") - ("print-pda-json", po::bool_switch(&print_pda_json), "Print PDA in JSON format to terminal.") // TODO: This is currently mostly a debug option. Make it useful! - ("print-solver-instance", po::value(&solver_instance_output), "Print SolverInstance in JSON format to file.") - ("input-solver-instance", po::value(&solver_instance_input), "Print SolverInstance in JSON format to file.") // TODO: Move option to Input Options... - ; opts.add(parsing.options()); opts.add(verifier.options()); - opts.add(output); + opts.add(output.options()); po::variables_map vm; po::store(po::parse_command_line(argc, argv, opts), vm); @@ -78,52 +105,21 @@ int main(int argc, const char** argv) { return 1; } - if (silent) { no_parser_warnings = true; } - if (!solver_instance_input.empty()) { - std::ifstream input_stream(solver_instance_input); - if (!input_stream.is_open()) { - std::stringstream es; - es << "error: Could not open file: " << solver_instance_input << std::endl; - throw std::runtime_error(es.str()); - } - auto solver_instance = parsing::SolverInstanceJsonParser::parse<>(input_stream); - // TODO: Make Verifier work with this solver_instance. (for now we just print it to console) - std::visit([](auto&& solver_instance){ - std::cout << solver_instance->to_json().dump() << std::endl; - }, solver_instance); - return 0; - } - - auto pda_variant = parsing.parse(no_parser_warnings); - if (!silent) { - std::cout << "Parsing duration: " << parsing.duration() << std::endl; + if (verifier.needs_trace_info_pair()) { + auto instance_variant = parsing.parse_instance(); + if (!output.silent) { std::cout << "Parsing duration: " << parsing.duration() << std::endl; } + std::visit([&verifier,&output](auto&& instance) { + output.do_output(instance); + verifier.verify(*instance); + }, instance_variant); + } else { + auto instance_variant = parsing.parse_instance<>(); + if (!output.silent) { std::cout << "Parsing duration: " << parsing.duration() << std::endl; } + std::visit([&verifier,&output](auto&& instance) { + output.do_output(instance); + verifier.verify<>(*instance); + }, instance_variant); } - if (print_pda_json) { - std::visit([](auto&& pda){ - std::cout << pda.to_json().dump() << std::endl; - }, pda_variant); - return 0; // TODO: What else.? - } - if (!solver_instance_output.empty()) { - std::ofstream out(solver_instance_output); - if (out.is_open()) { - std::visit([&out,&verifier](auto&& pda){ - auto instance = verifier.get_product(std::forward(pda)); - out << instance.to_json().dump() << std::endl; - }, pda_variant); - } else { - std::cerr << "Could not open --print-solver-instance\"" << solver_instance_output << "\" for writing" << std::endl; - exit(-1); - } - return 0; - } - std::visit([](auto&& pda){ - std::cout << "States: " << pda.states().size() << ". Labels: " << pda.number_of_labels() << std::endl; - }, pda_variant); - - std::visit([&verifier](auto&& pda) { - verifier.verify(std::forward(pda)); - }, pda_variant); return 0; } \ No newline at end of file diff --git a/src/pdaaal-bin/parsing/Parsing.cpp b/src/pdaaal-bin/parsing/Parsing.cpp index ebc97d7..d22cf27 100644 --- a/src/pdaaal-bin/parsing/Parsing.cpp +++ b/src/pdaaal-bin/parsing/Parsing.cpp @@ -109,14 +109,14 @@ namespace pdaaal::parsing { } } - Parsing::pda_variant_t Parsing::parse(bool no_warnings) { + Parsing::pda_variant_t Parsing::parse_pda(bool no_warnings) { std::stringstream dummy; parsing_options_t parse_opts(no_warnings ? dummy : std::cerr, get_format(input_format), get_weight_type(weight_type), use_state_names); parsing_stopwatch.start(); - auto value = (input_file.empty() || input_file == "-") + auto value = (pda_file.empty() || pda_file == "-") ? parse_stream(std::cin, parse_opts) - : parse_file(input_file, parse_opts); + : parse_file(pda_file, parse_opts); parsing_stopwatch.stop(); return value; } diff --git a/src/pdaaal-bin/parsing/Parsing.h b/src/pdaaal-bin/parsing/Parsing.h index 08131fc..f34f096 100644 --- a/src/pdaaal-bin/parsing/Parsing.h +++ b/src/pdaaal-bin/parsing/Parsing.h @@ -27,13 +27,14 @@ #ifndef PDAAAL_PARSING_H #define PDAAAL_PARSING_H +#include "PdaJsonParser.h" +#include "SolverInstanceJsonParser.h" +#include "PAutomatonParser.h" +#include #include #include #include -#include -#include "PdaJsonParser.h" - namespace po = boost::program_options; namespace pdaaal::parsing { @@ -47,26 +48,77 @@ namespace pdaaal::parsing { PdaSaxHandler, false>::pda_t, PdaSaxHandler, true>::pda_t, PdaSaxHandler, false>::pda_t>; + template + using solver_instance_variant_t = typename SolverInstanceSaxHandler::solver_instance_variant_t; explicit Parsing(const std::string& caption = "Input Options") : input_options{caption} { input_options.add_options() ("input", po::value(&input_file), "Input file. To read from std input specify '--input -'.") + ("msgpack", po::bool_switch(&msgpack), "Use the binary MessagePack input format") + ("pda,p", po::value(&pda_file), "PDA JSON file input.") ("format", po::value(&input_format), "Input format. pdaaal|moped (default=pdaaal).") ("weight", po::value(&weight_type), "Weight type. none|uint|int (default=none).") ("state-names", po::bool_switch(&use_state_names), "Enable named states (instead of index).") + ("initial-automaton,i", po::value(&initial_pa_file), "Initial PAutomaton file input.") + ("final-automaton,f", po::value(&final_pa_file), "Final PAutomaton file input.") + ("json-automata", po::bool_switch(&json_automata), "Parse P-automata files using JSON format.") ; } [[nodiscard]] const po::options_description& options() const { return input_options; } [[nodiscard]] double duration() const { return parsing_stopwatch.duration(); } - pda_variant_t parse(bool no_warnings = false); + pda_variant_t parse_pda(bool no_warnings = false); + + template + solver_instance_variant_t parse_instance() { + if (input_file.empty() && !pda_file.empty() && !initial_pa_file.empty() && !final_pa_file.empty()) { + // Parse from separate PDA and P-automata files. + parsing_stopwatch.start(); + auto pda = parse_pda(); + auto value = std::visit([this](auto&& pda){ + auto initial_p_automaton = json_automata ? + PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : + PAutomatonParser::parse_file(initial_pa_file, pda); + auto final_p_automaton = json_automata ? + PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : + PAutomatonParser::parse_file(final_pa_file, pda); + return solver_instance_variant_t(SolverInstance(std::forward(pda), std::move(initial_p_automaton), std::move(final_p_automaton))); + }, std::move(pda)); + parsing_stopwatch.stop(); + return value; + } else { + // Parse from single solver-instance file. + if (input_file.empty() || input_file == "-") { + return parse_instance_stream(std::cin); + } else { + std::ifstream input_stream(input_file); + if (!input_stream.is_open()) { + std::stringstream es; + es << "error: Could not open file: " << input_file << std::endl; + throw std::runtime_error(es.str()); + } + return parse_instance_stream(input_stream); + } + } + } private: + template + auto parse_instance_stream(std::istream& input_stream) { + parsing_stopwatch.start(); + auto value = SolverInstanceJsonParser::parse(input_stream, msgpack ? json::input_format_t::msgpack : json::input_format_t::json); + parsing_stopwatch.stop(); + return value; + } + po::options_description input_options; std::string input_file; std::string input_format = "pdaaal"; std::string weight_type = "none"; bool use_state_names = false; + bool msgpack = false; + std::string pda_file, initial_pa_file, final_pa_file, solver_instance_input; + bool json_automata = false; stopwatch parsing_stopwatch{false}; }; } diff --git a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h index 97e40bb..e99f714 100644 --- a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h +++ b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h @@ -97,12 +97,6 @@ namespace pdaaal::parsing { template struct pda_to_automaton_sax> { using type = std::variant...>; }; - template using pda_to_automaton_sax_t = typename pda_to_automaton_sax::type; - template struct get_automaton; - template struct get_automaton> { - using type = std::variant().get_automaton())...>; - }; - template using get_automaton_t = typename get_automaton::type; template struct deduce_solver_instance; template struct deduce_solver_instance> { using type = std::variant(), @@ -111,9 +105,9 @@ namespace pdaaal::parsing { }; using automaton_sax_variant_t = typename pda_to_automaton_sax::type; - using automaton_variant_t = typename get_automaton::type; + public: using solver_instance_variant_t = typename deduce_solver_instance::type; - + private: PdaTypeSaxHandler pda_type_sax_handler; std::optional pda_sax_handler; std::optional pda_variant; From 0a0b272b170c6faf7e3098c45fcfa8224e456e77 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Tue, 1 Mar 2022 13:28:19 +0100 Subject: [PATCH 12/16] P-automata in SolverInstance JSON: Initial states are optional --- src/include/pdaaal/PAutomaton.h | 14 +++++++--- src/include/pdaaal/PAutomatonProduct.h | 4 +-- src/pdaaal-bin/parsing/PAutomatonJsonParser.h | 26 +++++++++++-------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/include/pdaaal/PAutomaton.h b/src/include/pdaaal/PAutomaton.h index 4feaede..2eff30b 100644 --- a/src/include/pdaaal/PAutomaton.h +++ b/src/include/pdaaal/PAutomaton.h @@ -134,8 +134,8 @@ namespace pdaaal { template using pda_to_pautomaton_t = typename details::pda_to_pautomaton::type; - template - void to_json(json& j, const PAutomaton& automaton) { + template + void to_json_impl(json& j, const PAutomaton& automaton) { j = json::object(); size_t num_pda_states = automaton.pda().states().size(); auto state_to_json_value = [&automaton](size_t state) -> json { @@ -151,8 +151,10 @@ namespace pdaaal { json j_edges = json::array(); for (const auto& state : automaton.states()) { auto j_from = state_to_json_value(state->_id); - if (state->_id < num_pda_states) { - j["initial"].emplace_back(j_from); + if constexpr(with_initial) { + if (state->_id < num_pda_states) { + j["initial"].emplace_back(j_from); + } } if (state->_accepting) { j["accepting"].emplace_back(j_from); @@ -168,6 +170,10 @@ namespace pdaaal { } j["edges"] = j_edges; } + template + void to_json(json& j, const PAutomaton& automaton) { + to_json_impl(j,automaton); + } } diff --git a/src/include/pdaaal/PAutomatonProduct.h b/src/include/pdaaal/PAutomatonProduct.h index a80eaeb..bf9f462 100644 --- a/src/include/pdaaal/PAutomatonProduct.h +++ b/src/include/pdaaal/PAutomatonProduct.h @@ -399,8 +399,8 @@ namespace pdaaal { details::params_state_names(j.back(), instance.pda()); details::params_weight_type(j.back(), instance.pda()); j.emplace_back(instance.pda()); - j.emplace_back(instance.initial_automaton()); - j.emplace_back(instance.final_automaton()); + j.emplace_back(); to_json_impl(j.back(),instance.initial_automaton()); // Don't print initial states in P-automata, + j.emplace_back(); to_json_impl(j.back(),instance.final_automaton()); // since they are derived from the PDA. } } diff --git a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h index c1f61c2..8eb31b4 100644 --- a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h +++ b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h @@ -92,11 +92,11 @@ namespace pdaaal::parsing { case context_type::p_automaton: switch (flag) { case FLAG_1: - return keys::initial; - case FLAG_2: return keys::accepting; - case FLAG_3: + case FLAG_2: return keys::edges; + case FLAG_3: + return keys::initial; default: break; } @@ -109,7 +109,7 @@ namespace pdaaal::parsing { } }; - template + template class PAutomatonSaxHandler : public SAXHandlerContextStack { using parent_t = SAXHandlerContextStack; @@ -119,7 +119,7 @@ namespace pdaaal::parsing { static constexpr bool skip_state_mapping = pda_t::expose_skip_state_mapping; constexpr static context_t initial_context = context_object(); - constexpr static context_t p_automaton = context_object(); // TODO: Consider making "initial" optional (since it can be derived from the PDA). + constexpr static context_t p_automaton = context_object(); constexpr static context_t initial_array = context_array(); constexpr static context_t accepting_array = context_array(); constexpr static context_t edges_array = context_array(); @@ -299,18 +299,22 @@ namespace pdaaal::parsing { switch (current_context_type()) { case context_type::initial: if (key == "P-automaton") { - if (!handle_key()) return false; + return handle_key(); } else { return error_unexpected_key(key); } break; case context_type::p_automaton: - if (key == "initial") { - if (!handle_key()) return false; - } else if (key == "accepting") { - if (!handle_key()) return false; + if (key == "accepting") { + return handle_key(); } else if (key == "edges"){ - if (!handle_key()) return false; + return handle_key(); + } else if (key == "initial") { + if constexpr(require_initial) { + return handle_key(); + } else { + last_key = keys::initial; + } } else { return error_unexpected_key(key); } From 35739d20217d0b61a380e411d61b7b9e97b96fa0 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Tue, 1 Mar 2022 13:40:08 +0100 Subject: [PATCH 13/16] Make new P-automaton JSON parser the default --- src/pdaaal-bin/parsing/PAutomatonJsonParser.h | 14 ++++++++++++-- src/pdaaal-bin/parsing/Parsing.h | 4 ++-- test/Parser_test.cpp | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h index 8eb31b4..ca2c788 100644 --- a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h +++ b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h @@ -391,8 +391,18 @@ namespace pdaaal::parsing { } }; - class PAutomatonJsonParser_New { + class PAutomatonJsonParser { public: + template + static auto parse(const std::string& file, pda_t& pda) { + std::ifstream file_stream(file); + if (!file_stream.is_open()) { + std::stringstream error; + error << "Could not open P-automaton file: " << file << std::endl; + throw std::runtime_error(error.str()); + } + return parse(file_stream, pda); + } template static auto parse(std::istream& stream, pda_t& pda, json::input_format_t format = json::input_format_t::json) { std::stringstream error_stream; @@ -404,7 +414,7 @@ namespace pdaaal::parsing { } }; - class PAutomatonJsonParser { + class PAutomatonJsonParser_Old { public: template static auto parse(const std::string& file, pda_t& pda, const std::string& name = "P-automaton") { diff --git a/src/pdaaal-bin/parsing/Parsing.h b/src/pdaaal-bin/parsing/Parsing.h index f34f096..fee6f55 100644 --- a/src/pdaaal-bin/parsing/Parsing.h +++ b/src/pdaaal-bin/parsing/Parsing.h @@ -77,10 +77,10 @@ namespace pdaaal::parsing { auto pda = parse_pda(); auto value = std::visit([this](auto&& pda){ auto initial_p_automaton = json_automata ? - PAutomatonJsonParser::parse(initial_pa_file, pda, "P-automaton") : + PAutomatonJsonParser::parse(initial_pa_file, pda) : PAutomatonParser::parse_file(initial_pa_file, pda); auto final_p_automaton = json_automata ? - PAutomatonJsonParser::parse(final_pa_file, pda, "P-automaton") : + PAutomatonJsonParser::parse(final_pa_file, pda) : PAutomatonParser::parse_file(final_pa_file, pda); return solver_instance_variant_t(SolverInstance(std::forward(pda), std::move(initial_p_automaton), std::move(final_p_automaton))); }, std::move(pda)); diff --git a/test/Parser_test.cpp b/test/Parser_test.cpp index 9eadaef..dd69822 100644 --- a/test/Parser_test.cpp +++ b/test/Parser_test.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(PAutomatonFromJson_OldFormat_Test) {"accepting":true,"edges":[]} ] }})"); - auto automaton = parsing::PAutomatonJsonParser::parse<>(automaton_stream, pda); + auto automaton = parsing::PAutomatonJsonParser_Old::parse<>(automaton_stream, pda); std::vector stack; stack.emplace_back(0); bool result = Solver::post_star_accepts(automaton, 0, stack); BOOST_CHECK(result); @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(PAutomatonFromJson_NewFormat_Test) [1,"A",2] ] }})"); - auto automaton = parsing::PAutomatonJsonParser_New::parse<>(automaton_stream, pda); + auto automaton = parsing::PAutomatonJsonParser::parse<>(automaton_stream, pda); std::vector stack; stack.emplace_back(0); bool result = Solver::post_star_accepts(automaton, 0, stack); BOOST_CHECK(result); From e2460612db4df18517e51732d73e20fab6d6949d Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Tue, 1 Mar 2022 14:22:34 +0100 Subject: [PATCH 14/16] Move SaxHandlerHelpers to pdaaal/utils folder --- .../pdaaal/utils}/SaxHandlerHelpers.h | 105 +------------- src/pdaaal-bin/parsing/PAutomatonJsonParser.h | 2 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 2 +- src/pdaaal-bin/parsing/SaxHandlerDispatch.h | 134 ++++++++++++++++++ .../parsing/SolverInstanceJsonParser.h | 1 + 5 files changed, 139 insertions(+), 105 deletions(-) rename src/{pdaaal-bin/parsing => include/pdaaal/utils}/SaxHandlerHelpers.h (64%) create mode 100644 src/pdaaal-bin/parsing/SaxHandlerDispatch.h diff --git a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h b/src/include/pdaaal/utils/SaxHandlerHelpers.h similarity index 64% rename from src/pdaaal-bin/parsing/SaxHandlerHelpers.h rename to src/include/pdaaal/utils/SaxHandlerHelpers.h index de01996..88398e0 100644 --- a/src/pdaaal-bin/parsing/SaxHandlerHelpers.h +++ b/src/include/pdaaal/utils/SaxHandlerHelpers.h @@ -27,17 +27,14 @@ #ifndef PDAAAL_SAXHANDLERHELPERS_H #define PDAAAL_SAXHANDLERHELPERS_H -#include +#include "flags.h" #include #include -#include #include -#include -#include #include +#include namespace pdaaal::parsing { - // We wish to be able to stop parsing before the end of input, but still handle errors correctly. // Keep track of whether the error stream was used, and this way distinguish between error and early return. class SAXHandlerBase { @@ -241,104 +238,6 @@ namespace pdaaal::parsing { std::stack _context_stack; }; - // This class allows combining multiple SAX handlers by providing SAXHandlerDispatch to nlohmann::json::sax_parse, - // and using the _callback function to change the SAX handler dispatch functions during parsing. - // The _callback function is called, when the internal SAX handler function returns false, but has not used the error stream. - // This indirect approach is not the most efficient, but nlohmann/json does not support combining SAX handlers - // (at least not in the public interface), so this is good enough for now. - // An alternative could be to use a different JSON library, but the maturity of nlohmann/json is currently preferred. - class SAXHandlerDispatch { - public: - using number_integer_t = typename nlohmann::json::number_integer_t; - using number_unsigned_t = typename nlohmann::json::number_unsigned_t; - using number_float_t = typename nlohmann::json::number_float_t; - using string_t = typename nlohmann::json::string_t; - using binary_t = typename nlohmann::json::binary_t; - - template - SAXHandlerDispatch(other_handler_t& other_handler, CallbackFn&& callback) : _callback(std::forward(callback)) { - static_assert(std::is_base_of_v, - "The SAX handler needs to derive from SAXHandlerBase to allow checking if an error occurred when returning false."); - set_dispatch(other_handler); - } - template - constexpr void set_dispatch(other_handler_t& other_handler) { - static_assert(std::is_base_of_v); - _dispatch_null = [&](){ return other_handler.null(); }; - _dispatch_boolean = [&](bool value){ return other_handler.boolean(value); }; - _dispatch_number_integer = [&](number_integer_t value){ return other_handler.number_integer(value); }; - _dispatch_number_unsigned = [&](number_unsigned_t value){ return other_handler.number_unsigned(value); }; - _dispatch_number_float = [&](number_float_t value, const string_t& unused){ return other_handler.number_float(value,unused); }; - _dispatch_string = [&](string_t& value){ return other_handler.string(value); }; - _dispatch_binary = [&](binary_t& value){ return other_handler.binary(value); }; - _dispatch_key = [&](string_t& key){ return other_handler.key(key); }; - _dispatch_start_object = [&](std::size_t size){ return other_handler.start_object(size); }; - _dispatch_end_object = [&](){ return other_handler.end_object(); }; - _dispatch_start_array = [&](std::size_t size){ return other_handler.start_array(size); }; - _dispatch_end_array = [&](){ return other_handler.end_array(); }; - _dispatch_parse_error = [&](std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e){ return other_handler.parse_error(location, last_token, e); }; - _dispatch_is_errored = [&](){ return other_handler.is_errored(); }; - } - - bool null() { - return _dispatch_null() || (!_dispatch_is_errored() && _callback(*this)); - } - bool boolean(bool value) { - return _dispatch_boolean(value) || (!_dispatch_is_errored() && _callback(*this)); - } - bool number_integer(number_integer_t value) { - return _dispatch_number_integer(value) || (!_dispatch_is_errored() && _callback(*this)); - } - bool number_unsigned(number_unsigned_t value) { - return _dispatch_number_unsigned(value) || (!_dispatch_is_errored() && _callback(*this)); - } - bool number_float(number_float_t value, const string_t& unused) { - return _dispatch_number_float(value,unused) || (!_dispatch_is_errored() && _callback(*this)); - } - bool string(string_t& value) { - return _dispatch_string(value) || (!_dispatch_is_errored() && _callback(*this)); - } - bool binary(binary_t& value) { - return _dispatch_binary(value) || (!_dispatch_is_errored() && _callback(*this)); - } - bool key(string_t& key) { - return _dispatch_key(key) || (!_dispatch_is_errored() && _callback(*this)); - } - bool start_object(std::size_t size = std::size_t(-1)) { - return _dispatch_start_object(size) || (!_dispatch_is_errored() && _callback(*this)); - } - bool end_object() { - return _dispatch_end_object() || (!_dispatch_is_errored() && _callback(*this)); - } - bool start_array(std::size_t size = std::size_t(-1)) { - return _dispatch_start_array(size) || (!_dispatch_is_errored() && _callback(*this)); - } - bool end_array() { - return _dispatch_end_array() || (!_dispatch_is_errored() && _callback(*this)); - } - bool parse_error(std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e) { - return _dispatch_parse_error(location,last_token,e) || (!_dispatch_is_errored() && _callback(*this)); - } - - private: - std::function _callback; - - std::function _dispatch_null; - std::function _dispatch_boolean; - std::function _dispatch_number_integer; - std::function _dispatch_number_unsigned; - std::function _dispatch_number_float; - std::function _dispatch_string; - std::function _dispatch_binary; - std::function _dispatch_key; - std::function _dispatch_start_object; - std::function _dispatch_end_object; - std::function _dispatch_start_array; - std::function _dispatch_end_array; - std::function _dispatch_is_errored; - std::function _dispatch_parse_error; - }; - } #endif //PDAAAL_SAXHANDLERHELPERS_H diff --git a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h index ca2c788..9e1723c 100644 --- a/src/pdaaal-bin/parsing/PAutomatonJsonParser.h +++ b/src/pdaaal-bin/parsing/PAutomatonJsonParser.h @@ -27,7 +27,7 @@ #ifndef PDAAAL_PAUTOMATONJSONPARSER_H #define PDAAAL_PAUTOMATONJSONPARSER_H -#include "SaxHandlerHelpers.h" +#include #include namespace pdaaal::parsing { diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index b340f9d..9e9549a 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -27,7 +27,7 @@ #ifndef PDAAAL_PDAJSONPARSER_H #define PDAAAL_PDAJSONPARSER_H -#include "SaxHandlerHelpers.h" +#include #include #include #include diff --git a/src/pdaaal-bin/parsing/SaxHandlerDispatch.h b/src/pdaaal-bin/parsing/SaxHandlerDispatch.h new file mode 100644 index 0000000..120bb69 --- /dev/null +++ b/src/pdaaal-bin/parsing/SaxHandlerDispatch.h @@ -0,0 +1,134 @@ +/* + * 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 . + */ + +/* + * Copyright Morten K. Schou + */ + +/* + * File: SaxHandlerDispatch.h + * Author: Morten K. Schou + * + * Created on 01-03-2022. + */ + +#ifndef PDAAAL_SAXHANDLERDISPATCH_H +#define PDAAAL_SAXHANDLERDISPATCH_H + +#include + +namespace pdaaal::parsing { + + // This class allows combining multiple SAX handlers by providing SAXHandlerDispatch to nlohmann::json::sax_parse, + // and using the _callback function to change the SAX handler dispatch functions during parsing. + // The _callback function is called, when the internal SAX handler function returns false, but has not used the error stream. + // This indirect approach is not the most efficient, but nlohmann/json does not support combining SAX handlers + // (at least not in the public interface), so this is good enough for now. + // An alternative could be to use a different JSON library, but the maturity of nlohmann/json is currently preferred. + class SAXHandlerDispatch { + public: + using number_integer_t = typename nlohmann::json::number_integer_t; + using number_unsigned_t = typename nlohmann::json::number_unsigned_t; + using number_float_t = typename nlohmann::json::number_float_t; + using string_t = typename nlohmann::json::string_t; + using binary_t = typename nlohmann::json::binary_t; + + template + SAXHandlerDispatch(other_handler_t& other_handler, CallbackFn&& callback) : _callback(std::forward(callback)) { + static_assert(std::is_base_of_v, + "The SAX handler needs to derive from SAXHandlerBase to allow checking if an error occurred when returning false."); + set_dispatch(other_handler); + } + template + constexpr void set_dispatch(other_handler_t& other_handler) { + static_assert(std::is_base_of_v); + _dispatch_null = [&](){ return other_handler.null(); }; + _dispatch_boolean = [&](bool value){ return other_handler.boolean(value); }; + _dispatch_number_integer = [&](number_integer_t value){ return other_handler.number_integer(value); }; + _dispatch_number_unsigned = [&](number_unsigned_t value){ return other_handler.number_unsigned(value); }; + _dispatch_number_float = [&](number_float_t value, const string_t& unused){ return other_handler.number_float(value,unused); }; + _dispatch_string = [&](string_t& value){ return other_handler.string(value); }; + _dispatch_binary = [&](binary_t& value){ return other_handler.binary(value); }; + _dispatch_key = [&](string_t& key){ return other_handler.key(key); }; + _dispatch_start_object = [&](std::size_t size){ return other_handler.start_object(size); }; + _dispatch_end_object = [&](){ return other_handler.end_object(); }; + _dispatch_start_array = [&](std::size_t size){ return other_handler.start_array(size); }; + _dispatch_end_array = [&](){ return other_handler.end_array(); }; + _dispatch_parse_error = [&](std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e){ return other_handler.parse_error(location, last_token, e); }; + _dispatch_is_errored = [&](){ return other_handler.is_errored(); }; + } + + bool null() { + return _dispatch_null() || (!_dispatch_is_errored() && _callback(*this)); + } + bool boolean(bool value) { + return _dispatch_boolean(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool number_integer(number_integer_t value) { + return _dispatch_number_integer(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool number_unsigned(number_unsigned_t value) { + return _dispatch_number_unsigned(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool number_float(number_float_t value, const string_t& unused) { + return _dispatch_number_float(value,unused) || (!_dispatch_is_errored() && _callback(*this)); + } + bool string(string_t& value) { + return _dispatch_string(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool binary(binary_t& value) { + return _dispatch_binary(value) || (!_dispatch_is_errored() && _callback(*this)); + } + bool key(string_t& key) { + return _dispatch_key(key) || (!_dispatch_is_errored() && _callback(*this)); + } + bool start_object(std::size_t size = std::size_t(-1)) { + return _dispatch_start_object(size) || (!_dispatch_is_errored() && _callback(*this)); + } + bool end_object() { + return _dispatch_end_object() || (!_dispatch_is_errored() && _callback(*this)); + } + bool start_array(std::size_t size = std::size_t(-1)) { + return _dispatch_start_array(size) || (!_dispatch_is_errored() && _callback(*this)); + } + bool end_array() { + return _dispatch_end_array() || (!_dispatch_is_errored() && _callback(*this)); + } + bool parse_error(std::size_t location, const std::string& last_token, const nlohmann::detail::exception& e) { + return _dispatch_parse_error(location,last_token,e) || (!_dispatch_is_errored() && _callback(*this)); + } + + private: + std::function _callback; + + std::function _dispatch_null; + std::function _dispatch_boolean; + std::function _dispatch_number_integer; + std::function _dispatch_number_unsigned; + std::function _dispatch_number_float; + std::function _dispatch_string; + std::function _dispatch_binary; + std::function _dispatch_key; + std::function _dispatch_start_object; + std::function _dispatch_end_object; + std::function _dispatch_start_array; + std::function _dispatch_end_array; + std::function _dispatch_is_errored; + std::function _dispatch_parse_error; + }; + +} + +#endif //PDAAAL_SAXHANDLERDISPATCH_H diff --git a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h index e99f714..58aab77 100644 --- a/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h +++ b/src/pdaaal-bin/parsing/SolverInstanceJsonParser.h @@ -29,6 +29,7 @@ #include "PdaJsonParser.h" #include "PAutomatonJsonParser.h" +#include "SaxHandlerDispatch.h" #include #include From 69a371b38fe0d72f7faea951550142f7f2d21c95 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Tue, 1 Mar 2022 14:39:38 +0100 Subject: [PATCH 15/16] Check --engine option bounds + small simplify of main --- src/pdaaal-bin/Verifier.h | 13 ++++++++++++- src/pdaaal-bin/main.cpp | 27 +++++++++++++-------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/pdaaal-bin/Verifier.h b/src/pdaaal-bin/Verifier.h index 4c2aa9a..a8da1dc 100644 --- a/src/pdaaal-bin/Verifier.h +++ b/src/pdaaal-bin/Verifier.h @@ -90,6 +90,8 @@ namespace pdaaal { void verify(instance_t& instance) { using pda_t = std20::remove_cvref_t; + if (engine == 0) return; // No verification if not specified. + bool result = false; std::vector trace; if constexpr (trace_info_type == TraceInfoType::Single) { @@ -176,6 +178,11 @@ namespace pdaaal { } break; } + default: { + std::stringstream error; + error << "error: Unsupported option value --engine " << engine << std::endl; + throw std::runtime_error(error.str()); + } } } else { switch (engine) { @@ -217,6 +224,11 @@ namespace pdaaal { throw std::runtime_error("Cannot use fixed-point (longest or shortest) trace option for unweighted PDA."); } } + default: { + std::stringstream error; + error << "error: Unsupported option value --engine " << engine << std::endl; + throw std::runtime_error(error.str()); + } } } @@ -241,7 +253,6 @@ namespace pdaaal { po::options_description verification_options; size_t engine = 0; Trace_Type trace_type = Trace_Type::None; - //bool print_trace = false; }; } diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index 001962a..cdc2ba2 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -38,7 +38,7 @@ using namespace pdaaal; class main_output { public: - main_output(const std::string& caption = "Output Options") : output_options(caption) { + explicit main_output(const std::string& caption = "Output Options") : output_options(caption) { output_options.add_options() ("silent,s", po::bool_switch(&silent), "Disables non-essential output.") ("print-pda-json", po::value(&pda_json_output), "Print PDA in JSON format to terminal.") @@ -73,6 +73,15 @@ class main_output { std::string solver_instance_output, pda_json_output; }; +template +void run(parsing::Parsing& parsing, Verifier& verifier, main_output& output) { + auto instance_variant = parsing.parse_instance(); + if (!output.silent) { std::cout << "Parsing duration: " << parsing.duration() << std::endl; } + std::visit([&verifier,&output](auto&& instance) { + output.do_output(instance); + verifier.verify(*instance); + }, instance_variant); +} int main(int argc, const char** argv) { po::options_description opts; @@ -105,20 +114,10 @@ int main(int argc, const char** argv) { return 1; } - if (verifier.needs_trace_info_pair()) { - auto instance_variant = parsing.parse_instance(); - if (!output.silent) { std::cout << "Parsing duration: " << parsing.duration() << std::endl; } - std::visit([&verifier,&output](auto&& instance) { - output.do_output(instance); - verifier.verify(*instance); - }, instance_variant); + if (verifier.needs_trace_info_pair()) { // Currently, we need to differentiate between these cases at top level. + run(parsing, verifier, output); } else { - auto instance_variant = parsing.parse_instance<>(); - if (!output.silent) { std::cout << "Parsing duration: " << parsing.duration() << std::endl; } - std::visit([&verifier,&output](auto&& instance) { - output.do_output(instance); - verifier.verify<>(*instance); - }, instance_variant); + run(parsing, verifier, output); } return 0; From 0b6a1e5fd025bb3b5fa0f057e9a51184377b5c62 Mon Sep 17 00:00:00 2001 From: Morten Schou Date: Tue, 1 Mar 2022 15:16:12 +0100 Subject: [PATCH 16/16] small improvement --- src/include/pdaaal/utils/SaxHandlerHelpers.h | 7 ++++--- src/pdaaal-bin/main.cpp | 2 +- src/pdaaal-bin/parsing/Parsing.h | 2 +- src/pdaaal-bin/parsing/PdaJsonParser.h | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/include/pdaaal/utils/SaxHandlerHelpers.h b/src/include/pdaaal/utils/SaxHandlerHelpers.h index 88398e0..463e4d0 100644 --- a/src/include/pdaaal/utils/SaxHandlerHelpers.h +++ b/src/include/pdaaal/utils/SaxHandlerHelpers.h @@ -66,7 +66,7 @@ namespace pdaaal::parsing { using type_t = context_type; // Expose template parameter. using mask_t = utils::flag_mask; using flag_t = typename mask_t::flag_t; - using array_size_t = size_t; + using array_size_t = std::size_t; constexpr JsonParserContext(context_type type, flag_t flags, std::in_place_index_t<0>) noexcept : type(type), _v(std::in_place_index<0>, flags) {}; @@ -100,8 +100,8 @@ namespace pdaaal::parsing { // The template class should provide the following: // - 'enum class keys' with printing 'ostream& operator<<(ostream&,keys)'. // - 'enum class context_type' with printing 'ostream& operator<<(ostream&,context_type)'. - // - 'static constexpr size_t N', which is the largest number of keys needed. (N also determines to uint size for keeping track of array index). - // - 'static constexpr keys get_key(context_type,flag)' that for object contexts map the type and flag to the corresponding key (only needed if handle_key is used). + // - 'static constexpr size_t N', which is the largest number of keys needed. + // - 'static constexpr keys get_key(context_type,flag)' that for object contexts map the type and flag to the corresponding key (only needed if handle_key or error_missing_keys are used). template struct SAXHandlerContextStack : public SAXHandlerBase, public Helper { using keys = typename Helper::keys; @@ -127,6 +127,7 @@ namespace pdaaal::parsing { void pop_context() { _context_stack.pop(); } void push_context(const context_t& c) { _context_stack.push(c); } + void push_context(context_t&& c) { _context_stack.push(std::move(c)); } [[nodiscard]] bool no_context() const { return _context_stack.empty(); } [[nodiscard]] const context_t& current_context() const { return _context_stack.top(); } [[nodiscard]] context_t& current_context() { return _context_stack.top(); } diff --git a/src/pdaaal-bin/main.cpp b/src/pdaaal-bin/main.cpp index cdc2ba2..2e70b96 100644 --- a/src/pdaaal-bin/main.cpp +++ b/src/pdaaal-bin/main.cpp @@ -121,4 +121,4 @@ int main(int argc, const char** argv) { } return 0; -} \ No newline at end of file +} diff --git a/src/pdaaal-bin/parsing/Parsing.h b/src/pdaaal-bin/parsing/Parsing.h index fee6f55..5e17220 100644 --- a/src/pdaaal-bin/parsing/Parsing.h +++ b/src/pdaaal-bin/parsing/Parsing.h @@ -117,7 +117,7 @@ namespace pdaaal::parsing { std::string weight_type = "none"; bool use_state_names = false; bool msgpack = false; - std::string pda_file, initial_pa_file, final_pa_file, solver_instance_input; + std::string pda_file, initial_pa_file, final_pa_file; bool json_automata = false; stopwatch parsing_stopwatch{false}; }; diff --git a/src/pdaaal-bin/parsing/PdaJsonParser.h b/src/pdaaal-bin/parsing/PdaJsonParser.h index 9e9549a..6d92675 100644 --- a/src/pdaaal-bin/parsing/PdaJsonParser.h +++ b/src/pdaaal-bin/parsing/PdaJsonParser.h @@ -468,7 +468,8 @@ namespace pdaaal::parsing { } }; - + // The PDA-type can be prepended to the PDA JSON structure to indicate the parameters + // (weight type and whether states are named) for the PDA parser. struct PdaTypeSaxHelper { enum class keys : uint32_t { none, state_names, weight_type }; friend constexpr std::ostream& operator<<(std::ostream& s, keys key) {