diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a841b772c253dd35c10ed07f0d3946c3cc6ce7ec..c1bfe8ee328a2d2014399304b727c4b0ab85fc27 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ test:all: - cmake --build build --target unit_tests - ./build/src/tests/unit_tests - cmake --build build --target mfix-parser - - find -name inputs - - find -name inputs |xargs -l ./build/mfix-parser + # - find -name inputs + # - find -name inputs |xargs -l ./build/mfix-parser tags: - mfix-exa diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5adbfc1e6896cea26526fb31576048d7210518e..1b4d8ffc29428d1bd39252cdc3b8939d4b202071 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,9 @@ ################################################################################ add_library(parser + inputs_file.cpp parser.cpp + solver.cpp ) target_include_directories(parser PRIVATE diff --git a/src/double.hpp b/src/double.hpp deleted file mode 100644 index 80e8115f1fc206bf0827afa14a6c655baff30b15..0000000000000000000000000000000000000000 --- a/src/double.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2014-2019 Dr. Colin Hirsch and Daniel Frey -// Please see LICENSE for license or visit https://github.com/taocpp/PEGTL/ - -#ifndef TAO_PEGTL_SRC_EXAMPLES_PEGTL_DOUBLE_HPP -#define TAO_PEGTL_SRC_EXAMPLES_PEGTL_DOUBLE_HPP - -#include - -namespace double_ { -// A grammar for doubles suitable for std::stod without locale support. -// See also: http://en.cppreference.com/w/cpp/string/basic_string/stof - -using namespace TAO_PEGTL_NAMESPACE; - -// clang-format off - struct plus_minus : opt< one< '+', '-' > > {}; - struct dot : one< '.' > {}; - - struct inf : seq< istring< 'i', 'n', 'f' >, - opt< istring< 'i', 'n', 'i', 't', 'y' > > > {}; - - struct nan : seq< istring< 'n', 'a', 'n' >, - opt< one< '(' >, - plus< alnum >, - one< ')' > > > {}; - - template< typename D > - struct number : if_then_else< dot, - plus< D >, - seq< plus< D >, opt< dot, star< D > > > > {}; - - struct e : one< 'e', 'E' > {}; - struct p : one< 'p', 'P' > {}; - struct exponent : seq< plus_minus, plus< digit > > {}; - - struct decimal : seq< number< digit >, opt< e, exponent > > {}; - struct hexadecimal : seq< one< '0' >, one< 'x', 'X' >, number< xdigit >, opt< p, exponent > > {}; - - struct grammar : seq< plus_minus, sor< hexadecimal, decimal, inf, nan > > {}; -// clang-format on - -} // namespace double_ - -#endif diff --git a/src/inputs.hpp b/src/inputs.hpp index 0f91fb6acae040741ac40d42bdd1892789277e65..cffd5e4fa3437d9c0a8053e13f7bf47a8610288a 100644 --- a/src/inputs.hpp +++ b/src/inputs.hpp @@ -10,12 +10,42 @@ namespace parser { -using InputArray = std::vector; -using InputValue = std::variant; -using InputInfo = std::map; +using NumberArray = std::vector; +using StringArray = std::vector; +using InputInfo = std::map>; std::optional parse_inputs(std::string); } // namespace parser +namespace solver_settings { + +struct GeometryAxis { + double low; + double high; + bool periodic; +}; +struct GeometrySettings { + std::tuple axes; +}; +struct SolverSettings { + GeometrySettings geometry; +}; + +std::optional make_solver(parser::InputInfo); +} // namespace solver_settings + +namespace inputs_file { +class InputsFile { +public: + InputsFile(std::string inputs_path) : m_path(inputs_path) {} + solver_settings::SolverSettings load(); + void save(solver_settings::SolverSettings); + +protected: + std::string m_path; +}; + +} // namespace inputs_file + #endif diff --git a/src/inputs_file.cpp b/src/inputs_file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4117db3766058ed2d399feff2a063deaa5777fd6 --- /dev/null +++ b/src/inputs_file.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +#include "inputs.hpp" + +template struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...)->overloaded; // not needed as of C++20 + +namespace inputs_file { + +solver_settings::SolverSettings InputsFile::load() { + + std::ifstream infile(m_path); + std::stringstream buffer; + buffer << infile.rdbuf(); + infile.close(); + + auto maybe_inputinfo = parser::parse_inputs(buffer.str()); + assert(maybe_inputinfo.has_value()); + + auto inputinfo = maybe_inputinfo.value(); + + // for (auto it = inputinfo.begin(); it != inputinfo.end(); ++it) { + // std::visit( + // overloaded{ + // [](std::string ss) { std::cout << ss << std::endl; }, + // [](parser::NumberArray aa) { std::cout << aa.size() << std::endl; + // }, + // [](parser::StringArray aa) { std::cout << aa.size() << std::endl; + // }, + // }, + // it->second); + // } + + auto maybe_solversettings = solver_settings::make_solver(inputinfo); + assert(maybe_solversettings.has_value()); + return maybe_solversettings.value(); + + solver_settings::SolverSettings ss; + return ss; +}; + +void InputsFile::save(solver_settings::SolverSettings settings) { + + std::ofstream out(m_path); + + auto [xx, yy, zz] = settings.geometry.axes; + out << std::endl; + out << " geometry.is_periodic = " << xx.periodic << " " << yy.periodic << " " + << zz.periodic << std::endl; + ; + out << " geometry.prob_lo = " << xx.low << " " << yy.low << " " << zz.low + << std::endl; + ; + out << " geometry.prob_hi = " << xx.high << " " << yy.high << " " + << zz.high; + ; + out.close(); +}; + +} // namespace inputs_file diff --git a/src/parser.cpp b/src/parser.cpp index 10ac228f3dd49501b6740e2ae62c615fe3449832..5d7bcca418ddb52cc668e271b30c4c6fce8967d8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6,17 +6,17 @@ #include // includes -#include "double.hpp" #include "inputs.hpp" -using namespace TAO_PEGTL_NAMESPACE; +using namespace tao::pegtl; namespace { struct parser_state { std::string current_key; - std::vector current_vec; - parser::InputValue current_value; + std::variant, std::vector> current_value; + std::vector current_num_vec; + std::vector current_str_vec; parser::InputInfo info; }; @@ -32,31 +32,39 @@ struct escaped_c struct escaped : sor {}; +struct key : plus, one<'_'>, one<'-'>, one<'.'>>> {}; + struct character : if_must_else, escaped, utf8::range<0x20, 0x10FFFF>> {}; -struct string_literal : if_must, until, character>> {}; -struct implicit_string : if_must> {}; - -struct whitespace_delimited_array : list>> {}; +struct quoted_string : if_must, until, character>> {}; +struct unquoted_string : key {}; +struct string_literal : sor {}; -struct L_FUN : pad, space> {}; -struct R_FUN : pad, space> {}; -struct L_ARR : pad, space> {}; -struct R_ARR : pad, space> {}; -struct L_BLK : pad, space> {}; -struct R_BLK : pad, space> {}; -struct S_CLN : pad, space> {}; +struct plus_minus : opt> {}; +struct dot : one<'.'> {}; -struct name : identifier {}; +struct decimal + : if_then_else, seq, opt>>> { +}; +struct e : one<'e', 'E'> {}; +struct p : one<'p', 'P'> {}; +struct exponent : seq> {}; +struct double_ : seq> {}; -struct value - : sor {}; +struct num_array : list>> {}; +struct str_array : list>> {}; +struct value : sor {}; struct comment : if_must, until> {}; -struct key : list> {}; -struct keyval : seq, string<'='>, pad> {}; -struct grammar : pad, space> {}; +struct value_item : pad {}; +struct value_list : list_must> {}; +struct keyval_line + : if_must, string<'='>, value_list, sor> {}; +struct comment_line : seq, until> {}; +struct blank_line : seq>, until> {}; +struct line : sor {}; +struct grammar : until {}; template struct action {}; @@ -68,45 +76,56 @@ template <> struct action { } }; -template <> struct action { +template <> struct action { template static void apply(const Input &in, parser_state &st) { std::stringstream ss(in.string()); std::string v; ss >> v; - st.current_value = v.substr(1, v.length() - 2); // strip quotes + auto str_val = v.substr(1, v.length() - 2); // strip quotes + st.current_str_vec.push_back(str_val); } }; -template <> struct action { +template <> struct action { template static void apply(const Input &in, parser_state &st) { std::stringstream ss(in.string()); std::string v; ss >> v; - st.current_value = v; + st.current_str_vec.push_back(v); } }; -template <> struct action { +template <> struct action { template static void apply(const Input &in, parser_state &st) { std::stringstream ss(in.string()); double v; ss >> v; - st.current_vec.push_back(v); + st.current_num_vec.push_back(v); + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + st.current_value = st.current_num_vec; + st.current_num_vec.clear(); } }; -template <> struct action { + +template <> struct action { template static void apply(const Input &in, parser_state &st) { std::stringstream ss(in.string()); - st.current_value = st.current_vec; - st.current_vec.clear(); + st.current_value = st.current_str_vec; + st.current_str_vec.clear(); } }; -template <> struct action { +template <> struct action { template static void apply(const Input &in, parser_state &st) { st.info[st.current_key] = st.current_value; diff --git a/src/solver.cpp b/src/solver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a95db811ee0d998268ac3bfb3c1455f38ee3ce51 --- /dev/null +++ b/src/solver.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "inputs.hpp" + +namespace { + +void require(parser::InputInfo ii, std::string key) { + if (!ii.count(key)) { + std::cout << "missing required key: " << key << std::endl; + } +} + +std::optional +do_make_solver(parser::InputInfo ii) { + solver_settings::SolverSettings ss; + require(ii, "geometry.prob_lo"); + require(ii, "geometry.prob_hi"); + require(ii, "geometry.is_periodic"); + auto lows = std::get(ii["geometry.prob_lo"]); + auto highs = std::get(ii["geometry.prob_hi"]); + auto is_periodic = std::get(ii["geometry.is_periodic"]); + std::get<0>(ss.geometry.axes).high = highs[0]; + std::get<0>(ss.geometry.axes).low = lows[0]; + std::get<0>(ss.geometry.axes).periodic = is_periodic[0]; + std::get<1>(ss.geometry.axes).high = highs[1]; + std::get<1>(ss.geometry.axes).low = lows[1]; + std::get<1>(ss.geometry.axes).periodic = is_periodic[1]; + std::get<2>(ss.geometry.axes).high = highs[2]; + std::get<2>(ss.geometry.axes).low = lows[2]; + std::get<2>(ss.geometry.axes).periodic = is_periodic[2]; + return ss; +} +} // namespace + +namespace solver_settings { + +std::optional +make_solver(parser::InputInfo ii) { + auto maybe_state = do_make_solver(ii); + if (!maybe_state.has_value()) { + return std::nullopt; + } + return maybe_state.value(); +} +} // namespace solver_settings diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index efe4fd1ea55a6aa7e5a0267d440d08c58f06d7ab..355c820fdf0b5e17ed4611eeee0558a268b02169 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -3,9 +3,13 @@ ################################################################################ add_executable(unit_tests EXCLUDE_FROM_ALL + ../inputs_file.cpp ../parser.cpp - parser/main.cpp - parser/inputs.t.cpp + ../solver.cpp + inputs.t.cpp + parser.t.cpp + main.cpp + solver.t.cpp ) target_include_directories(unit_tests PRIVATE diff --git a/src/tests/inputs.t.cpp b/src/tests/inputs.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..250a93fa814bbc90ed5f211067a6f89e604e739b --- /dev/null +++ b/src/tests/inputs.t.cpp @@ -0,0 +1,37 @@ +// tests + +#include "catch2/catch.hpp" + +#include +#include +#include +#include + +TEST_CASE("load_save_file", "[]") { + std::string test_path("abc"); + std::ofstream outfile(test_path); + + std::string data(R"( + geometry.is_periodic = 0 0 0 # Is periodic in each direction? + geometry.prob_lo = 0. 0. 0. # lo corner of physical domain + geometry.prob_hi = 0.004 0.001 0.001 # hi corner of physical domain + )"); + + outfile << data; + outfile.close(); + + inputs_file::InputsFile test_file(test_path); + auto solver_setup = test_file.load(); + + std::get<1>(solver_setup.geometry.axes).periodic = 1; + test_file.save(solver_setup); + + std::ifstream infile(test_path); + std::stringstream buffer; + buffer << infile.rdbuf(); + infile.close(); + CHECK(buffer.str() == R"( + geometry.is_periodic = 0 1 0 + geometry.prob_lo = 0 0 0 + geometry.prob_hi = 0.004 0.001 0.001)"); +} diff --git a/src/tests/parser/main.cpp b/src/tests/main.cpp similarity index 100% rename from src/tests/parser/main.cpp rename to src/tests/main.cpp diff --git a/src/tests/parser/inputs.t.cpp b/src/tests/parser.t.cpp similarity index 79% rename from src/tests/parser/inputs.t.cpp rename to src/tests/parser.t.cpp index dad519c2d12e27b282d2a69406cbb9f77ee5f6ff..cada1ec36b4f3becf0383bfc09acbb06163f6ca6 100644 --- a/src/tests/parser/inputs.t.cpp +++ b/src/tests/parser.t.cpp @@ -9,14 +9,14 @@ TEST_CASE("comment", "[]") { CHECK(maybe_st.has_value()); } -TEST_CASE("double_int", "[]") { +TEST_CASE("integer", "[]") { auto maybe_st = parser::parse_inputs(R"( mfix.max_step = 100 )"); REQUIRE(maybe_st.has_value()); auto info = maybe_st.value(); auto value = info["mfix.max_step"]; - auto aa = std::get(value); + auto aa = std::get(value); CHECK(aa.size() == 1); CHECK(aa[0] == 100); } @@ -28,7 +28,7 @@ mfix.stop_time = 0.20 REQUIRE(maybe_st.has_value()); auto info = maybe_st.value(); auto value = info["mfix.stop_time"]; - auto aa = std::get(value); + auto aa = std::get(value); CHECK(aa.size() == 1); CHECK(aa[0] == 0.2); } @@ -40,8 +40,9 @@ TEST_CASE("string", "[]") { REQUIRE(maybe_st.has_value()); auto info = maybe_st.value(); auto value = info["mfix.particle_init_type"]; - auto ss = std::get(value); - CHECK(ss == "Auto"); + auto ss = std::get(value); + CHECK(ss.size() == 1); + CHECK(ss[0] == "Auto"); } TEST_CASE("implicit string", "[]") { @@ -51,11 +52,12 @@ fluid.solve = fluid REQUIRE(maybe_st.has_value()); auto info = maybe_st.value(); auto value = info["fluid.solve"]; - auto ss = std::get(value); - CHECK(ss == "fluid"); + auto ss = std::get(value); + CHECK(ss.size() == 1); + CHECK(ss[0] == "fluid"); } -TEST_CASE("array", "[]") { +TEST_CASE("double array", "[]") { auto maybe_st = parser::parse_inputs(R"( mfix.gravity = -9.81 0.0 0.0 )"); @@ -63,12 +65,61 @@ TEST_CASE("array", "[]") { REQUIRE(maybe_st.has_value()); auto info = maybe_st.value(); auto value = info["mfix.gravity"]; - auto aa = std::get(value); + auto aa = std::get(value); CHECK(aa.size() == 3); CHECK(aa[0] == -9.81); CHECK(aa[1] == 0.0); CHECK(aa[2] == 0.0); } +TEST_CASE("string array", "[]") { + auto maybe_st = parser::parse_inputs(R"( + mfix.regions = abc ijk xyz +)"); + + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + auto value = info["mfix.regions"]; + auto aa = std::get(value); + CHECK(aa.size() == 3); + CHECK(aa[0] == "abc"); + CHECK(aa[1] == "ijk"); + CHECK(aa[2] == "xyz"); +} + +TEST_CASE("multiple arrays", "[]") { + auto maybe_st = parser::parse_inputs(R"( +geometry.prob_lo = -4 -1 -2 +geometry.prob_hi = 0.004 0.001 0.001 +geometry.is_periodic = 1 0 1 +)"); + + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + CHECK(info.count("geometry.prob_hi")); + CHECK(info.count("geometry.prob_lo")); + CHECK(info.count("geometry.is_periodic")); + + auto highs = info["geometry.prob_lo"]; + auto aa = std::get(highs); + CHECK(aa.size() == 3); + CHECK(aa[0] == -4); + CHECK(aa[1] == -1); + CHECK(aa[2] == -2); + + auto lows = info["geometry.prob_hi"]; + auto bb = std::get(lows); + CHECK(bb.size() == 3); + CHECK(bb[0] == 0.004); + CHECK(bb[1] == 0.001); + CHECK(bb[2] == 0.001); + + auto pers = info["geometry.is_periodic"]; + auto cc = std::get(pers); + CHECK(cc.size() == 3); + CHECK(cc[0] == 1); + CHECK(cc[1] == 0); + CHECK(cc[2] == 1); +} TEST_CASE("whole_file", "[]") { auto maybe_st = parser::parse_inputs(R"( diff --git a/src/tests/solver.t.cpp b/src/tests/solver.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..104110b20ca2bca73d9a84539b2a02aecf662fa4 --- /dev/null +++ b/src/tests/solver.t.cpp @@ -0,0 +1,48 @@ +// tests + +#include "catch2/catch.hpp" + +#include + +TEST_CASE("from_origin", "[]") { + parser::InputInfo ii; + ii["geometry.prob_lo"] = parser::NumberArray({0, 0, 0}); + ii["geometry.prob_hi"] = parser::NumberArray({0.004, 0.001, 0.001}); + ii["geometry.is_periodic"] = parser::NumberArray({0, 0, 0}); + auto maybe_sv = solver_settings::make_solver(ii); + + REQUIRE(maybe_sv.has_value()); + auto sv = maybe_sv.value(); + auto [xx, yy, zz] = sv.geometry.axes; + CHECK(xx.high == 0.004); + CHECK(xx.low == 0); + CHECK(yy.high == 0.001); + CHECK(yy.low == 0); + CHECK(zz.high == 0.001); + CHECK(zz.low == 0); + CHECK_FALSE(xx.periodic); + CHECK_FALSE(yy.periodic); + CHECK_FALSE(zz.periodic); +} + +TEST_CASE("negative_positive", "[]") { + parser::InputInfo ii; + ii["geometry.prob_lo"] = std::vector({-4, -5, -6}); + ii["geometry.prob_hi"] = std::vector({11.1, 22.2, 33.3}); + ii["geometry.is_periodic"] = parser::NumberArray({1, 0, 1}); + + auto maybe_sv = solver_settings::make_solver(ii); + + REQUIRE(maybe_sv.has_value()); + auto sv = maybe_sv.value(); + auto [xx, yy, zz] = sv.geometry.axes; + CHECK(xx.high == 11.1); + CHECK(xx.low == -4); + CHECK(xx.periodic); + CHECK(yy.high == 22.2); + CHECK(yy.low == -5); + CHECK(zz.high == 33.3); + CHECK(zz.low == -6); + CHECK(zz.periodic); + CHECK_FALSE(yy.periodic); +} diff --git a/subprojects/PEGTL b/subprojects/PEGTL index 054e44694f6c8cd24259b70f48cc09d84a9af395..ede3ce17aef7da27648b195dc77814cb1d3c313d 160000 --- a/subprojects/PEGTL +++ b/subprojects/PEGTL @@ -1 +1 @@ -Subproject commit 054e44694f6c8cd24259b70f48cc09d84a9af395 +Subproject commit ede3ce17aef7da27648b195dc77814cb1d3c313d