diff --git a/src/main.cpp b/src/main.cpp index 61ffefaf34..1dbcc857f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" @@ -51,6 +52,7 @@ * \brief Main NMODL code generation program */ +namespace fs = std::filesystem; using namespace nmodl; using namespace codegen; using namespace visitor; @@ -62,7 +64,7 @@ int main(int argc, const char* argv[]) { Version::to_string())}; /// list of mod files to process - std::vector mod_files; + std::vector mod_files; /// true if debug logger statements should be shown std::string verbose("info"); @@ -261,8 +263,8 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); - utils::make_path(output_dir); - utils::make_path(scratch_dir); + fs::create_directories(output_dir); + fs::create_directories(scratch_dir); if (sympy_opt) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() @@ -281,9 +283,9 @@ int main(int argc, const char* argv[]) { }; for (const auto& file: mod_files) { - logger->info("Processing {}", file); + logger->info("Processing {}", file.string()); - const auto modfile = utils::remove_extension(utils::base_name(file)); + const auto modfile = file.stem().string(); /// create file path for nmodl file auto filepath = [scratch_dir, modfile](const std::string& suffix) { diff --git a/src/parser/nmodl_driver.cpp b/src/parser/nmodl_driver.cpp index 8972471404..37b3979851 100644 --- a/src/parser/nmodl_driver.cpp +++ b/src/parser/nmodl_driver.cpp @@ -5,6 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include #include #include @@ -12,6 +13,8 @@ #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" +namespace fs = std::filesystem; + namespace nmodl { namespace parser { @@ -30,9 +33,9 @@ std::shared_ptr NmodlDriver::parse_stream(std::istream& in) { return astRoot; } -std::shared_ptr NmodlDriver::parse_file(const std::string& filename, +std::shared_ptr NmodlDriver::parse_file(const fs::path& filename, const location* loc) { - std::ifstream in(filename.c_str()); + std::ifstream in(filename); if (!in.good()) { std::ostringstream oss; if (loc == nullptr) { @@ -47,26 +50,25 @@ std::shared_ptr NmodlDriver::parse_file(const std::string& filenam } auto current_stream_name = stream_name; - stream_name = filename; - auto absolute_path = utils::cwd() + utils::pathsep + filename; + stream_name = filename.string(); + auto absolute_path = fs::absolute(filename); { - const auto last_slash = filename.find_last_of(utils::pathsep); - if (utils::file_is_abs(filename)) { - const auto path_prefix = filename.substr(0, last_slash + 1); + if (filename.is_absolute()) { + const auto path_prefix = filename.parent_path(); library.push_current_directory(path_prefix); - absolute_path = filename; - } else if (last_slash == std::string::npos) { - library.push_current_directory(utils::cwd()); + absolute_path = filename.string(); + } else if (!filename.has_parent_path()) { + library.push_current_directory(fs::current_path()); } else { - const auto path_prefix = filename.substr(0, last_slash + 1); - const auto path = utils::cwd() + utils::pathsep + path_prefix; + const auto path_prefix = filename.parent_path(); + const auto path = fs::absolute(path_prefix); library.push_current_directory(path); } } - open_files.emplace(absolute_path, loc); + open_files.emplace(absolute_path.string(), loc); parse_stream(in); - open_files.erase(absolute_path); + open_files.erase(absolute_path.string()); library.pop_current_directory(); stream_name = current_stream_name; @@ -79,24 +81,24 @@ std::shared_ptr NmodlDriver::parse_string(const std::string& input return astRoot; } -std::shared_ptr NmodlDriver::parse_include(const std::string& name, +std::shared_ptr NmodlDriver::parse_include(const fs::path& name, const location& loc) { if (name.empty()) { parse_error(loc, "empty filename"); } // Try to find directory containing the file to import - const auto directory_path = library.find_file(name); + const auto directory_path = fs::path{library.find_file(name)}; // Complete path of file (directory + filename). - std::string absolute_path = name; + auto absolute_path = name; if (!directory_path.empty()) { - absolute_path = directory_path + std::string(1, utils::pathsep) + name; + absolute_path = directory_path / name; } // Detect recursive inclusion. - auto already_included = open_files.find(absolute_path); + auto already_included = open_files.find(absolute_path.string()); if (already_included != open_files.end()) { std::ostringstream oss; oss << name << ": recursive inclusion.\n"; @@ -113,7 +115,7 @@ std::shared_ptr NmodlDriver::parse_include(const std::string& name program.swap(astRoot); auto filename_node = std::shared_ptr( - new ast::String(std::string(1, '"') + name + std::string(1, '"'))); + new ast::String(fmt::format("\"{}\"", name.string()))); return std::shared_ptr(new ast::Include(filename_node, program->get_blocks())); } diff --git a/src/parser/nmodl_driver.hpp b/src/parser/nmodl_driver.hpp index 67ddefe744..6466d750f1 100644 --- a/src/parser/nmodl_driver.hpp +++ b/src/parser/nmodl_driver.hpp @@ -12,6 +12,7 @@ * \brief Parser implementations */ +#include #include #include @@ -110,10 +111,11 @@ class NmodlDriver { * \param loc optional location when \a filename is dictated * by an `INCLUDE` NMODL directive. */ - std::shared_ptr parse_file(const std::string& filename, + std::shared_ptr parse_file(const std::filesystem::path& filename, const location* loc = nullptr); //// parse file specified in nmodl include directive - std::shared_ptr parse_include(const std::string& filename, const location& loc); + std::shared_ptr parse_include(const std::filesystem::path& filename, + const location& loc); void set_verbose(bool b) { verbose = b; diff --git a/src/units/units.cpp b/src/units/units.cpp index 4d8e56a395..2093e6f3a5 100644 --- a/src/units/units.cpp +++ b/src/units/units.cpp @@ -25,10 +25,6 @@ * \brief Units processing while being processed from lexer and parser */ -namespace { -constexpr std::size_t output_precision{8}; -} - namespace nmodl { namespace units { @@ -314,10 +310,9 @@ void UnitTable::print_units_sorted(std::ostream& units_details) const { table.end()); std::sort(sorted_elements.begin(), sorted_elements.end()); for (const auto& it: sorted_elements) { - units_details << fmt::format("{} {:.{}f}: {}\n", + units_details << fmt::format("{} {:g}: {}\n", it.first, it.second->get_factor(), - output_precision, fmt::join(it.second->get_dimensions(), " ")); } } diff --git a/src/utils/common_utils.cpp b/src/utils/common_utils.cpp index 62db3c6e80..7601ada626 100644 --- a/src/utils/common_utils.cpp +++ b/src/utils/common_utils.cpp @@ -25,83 +25,6 @@ namespace nmodl { namespace utils { -bool is_dir_exist(const std::string& path) { - struct stat info {}; - if (stat(path.c_str(), &info) != 0) { - return false; - } - return (info.st_mode & S_IFDIR) != 0; -} - -bool file_exists(const std::string& path) { - struct stat info {}; - return stat(path.c_str(), &info) == 0; -} - -bool file_is_abs(const std::string& path) { -#ifdef IS_WINDOWS - return path.find(":\\") != std::string::npos; -#else - return path.find(pathsep) == 0; -#endif -} - -std::string cwd() { - std::array cwd{}; - if (nullptr == getcwd(cwd.data(), MAXPATHLEN + 1)) { - throw std::runtime_error("working directory name too long"); - } - return {cwd.data()}; -} -bool make_path(const std::string& path) { - mode_t mode = 0755; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - int ret = mkdir(path.c_str(), mode); - if (ret == 0) { - return true; - } - - switch (errno) { - case ENOENT: - // parent didn't exist, try to create it - { - auto const pos = path.find_last_of('/'); - if (pos == std::string::npos) { - return false; - } - if (!make_path(path.substr(0, pos))) { - return false; - } - } - // now, try to create again - return 0 == mkdir(path.c_str(), mode); - - case EEXIST: - // done! - return is_dir_exist(path); - - default: - auto msg = "Can not create directory " + path; - throw std::runtime_error(msg); - } -} - -TempFile::TempFile(std::string path) - : path_(std::move(path)) { - std::ofstream output(path_); -} - -TempFile::TempFile(std::string path, const std::string& content) - : path_(std::move(path)) { - std::ofstream output(path_); - output << content; -} - -TempFile::~TempFile() { - if (remove(path_.c_str()) != 0) { - perror("Cannot delete temporary file"); - } -} - std::string generate_random_string(const int len, UseNumbersInString use_numbers) { std::string s(len, 0); constexpr std::size_t number_of_numbers{10}; diff --git a/src/utils/common_utils.hpp b/src/utils/common_utils.hpp index 650cdab0cb..b78f4f8eb2 100644 --- a/src/utils/common_utils.hpp +++ b/src/utils/common_utils.hpp @@ -40,62 +40,14 @@ bool is_last(Iter iter, const Cont& cont) { return ((iter != cont.end()) && (next(iter) == cont.end())); } -/// Given full file path, returns only name of the file -template -T base_name(T const& path, T const& delims = "/\\") { - return path.substr(path.find_last_of(delims) + 1); -} - -/// Given the file name, returns name of the file without extension -template -T remove_extension(T const& filename) { - typename T::size_type const p(filename.find_last_of('.')); - return p > 0 && p != T::npos ? filename.substr(0, p) : filename; -} - #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) -/// The character used by the operating system to separate pathname components -static constexpr char pathsep{'\\'}; /// The character conventionally used by the operating system to separate search path components static constexpr char envpathsep{';'}; -/// Maximum size of a directory path -static constexpr int max_path_len{_MAX_DIR}; #else -/// The character used by the operating system to separate pathname components -static constexpr char pathsep{'/'}; /// The character conventionally used by the operating system to separate search path components static constexpr char envpathsep{':'}; -/// Maximum size of a directory path -static constexpr int max_path_len{MAXPATHLEN}; #endif -/// Given directory path, create sub-directories -bool make_path(const std::string& path); - -/// Check if directory with given path exists -bool is_dir_exist(const std::string& path); - -/// Check if specified file path exists -bool file_exists(const std::string& path); - -/// Check if specified file path is absolute -bool file_is_abs(const std::string& path); - -/// get current working directory -std::string cwd(); - -/** - * \brief Create an empty file which is then removed when the C++ object is destructed - */ -struct TempFile { - explicit TempFile(std::string path); - TempFile(std::string path, const std::string& content); - ~TempFile(); - - private: - std::string path_; -}; - /// Enum to wrap bool variable to select if random string /// should have numbers or not enum UseNumbersInString : bool { WithNumbers = true, WithoutNumbers = false }; diff --git a/src/utils/file_library.cpp b/src/utils/file_library.cpp index f058b85119..480a0ffbf7 100644 --- a/src/utils/file_library.cpp +++ b/src/utils/file_library.cpp @@ -8,12 +8,15 @@ #include "file_library.hpp" #include +#include #include #include #include "utils/common_utils.hpp" #include "utils/string_utils.hpp" +namespace fs = std::filesystem; + namespace nmodl { @@ -24,44 +27,40 @@ FileLibrary FileLibrary::default_instance() { return library; } -void FileLibrary::append_dir(const std::string& path) { - paths_.insert(paths_.begin(), path); -} - void FileLibrary::append_env_var(const std::string& env_var) { const auto value = getenv(env_var.c_str()); if (value != nullptr) { for (const auto& path: stringutils::split_string(value, utils::envpathsep)) { if (!path.empty()) { - append_dir(path); + paths_.insert(paths_.begin(), path); } } } } -void FileLibrary::push_current_directory(const std::string& path) { +void FileLibrary::push_current_directory(const fs::path& path) { paths_.push_back(path); } void FileLibrary::push_cwd() { - push_current_directory(utils::cwd()); + push_current_directory(fs::current_path()); } void FileLibrary::pop_current_directory() { assert(!paths_.empty()); - paths_.erase(--paths_.end()); + if (!paths_.empty()) { + paths_.pop_back(); + } } -std::string FileLibrary::find_file(const std::string& file) { - if (utils::file_is_abs(file)) { - if (utils::file_exists(file)) { - return ""; - } +std::string FileLibrary::find_file(const fs::path& file) { + if (file.is_absolute() && fs::exists(file)) { + return ""; } for (auto paths_it = paths_.rbegin(); paths_it != paths_.rend(); ++paths_it) { - auto file_abs = *paths_it + utils::pathsep + file; - if (utils::file_exists(file_abs)) { - return *paths_it; + auto file_abs = *paths_it / file; + if (fs::exists(file_abs)) { + return paths_it->string(); } } return ""; diff --git a/src/utils/file_library.hpp b/src/utils/file_library.hpp index 17fbeb3fa1..e587799647 100644 --- a/src/utils/file_library.hpp +++ b/src/utils/file_library.hpp @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -41,7 +42,6 @@ class FileLibrary { * \name Managing inclusion paths. * \{ */ - void append_dir(const std::string& path); void append_env_var(const std::string& env_var); /** \} */ @@ -49,7 +49,7 @@ class FileLibrary { * \name current directory * \{ */ - void push_current_directory(const std::string& path); + void push_current_directory(const std::filesystem::path& path); void pop_current_directory(); /** \} */ @@ -58,14 +58,14 @@ class FileLibrary { * Determine real path of \a file * \return Directory containing \a file, or "" if not found. */ - std::string find_file(const std::string& file); + std::string find_file(const std::filesystem::path& file); private: /// push the working directory in the directories stack void push_cwd(); /// inclusion path list - std::vector paths_; + std::vector paths_; }; } // namespace nmodl diff --git a/src/visitors/main.cpp b/src/visitors/main.cpp index 8ca9795b68..945f3883f5 100644 --- a/src/visitors/main.cpp +++ b/src/visitors/main.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include +#include #include "ast/program.hpp" #include "config/config.h" @@ -33,6 +34,7 @@ using namespace nmodl; using namespace visitor; +namespace fs = std::filesystem; /** * \file @@ -64,7 +66,7 @@ int main(int argc, const char* argv[]) { fmt::format("NMODL Visitor : Runs standalone visitor classes({})", Version::to_string())}; bool verbose = false; - std::vector files; + std::vector files; app.add_flag("-v,--verbose", verbose, "Enable debug log level"); app.add_option("-f,--file,file", files, "One or more MOD files to process") @@ -107,9 +109,9 @@ int main(int argc, const char* argv[]) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); for (const auto& filename: files) { - logger->info("Processing {}", filename); + logger->info("Processing {}", filename.string()); - const std::string mod_file(utils::remove_extension(utils::base_name(filename))); + const std::string mod_file = filename.stem().string(); /// driver object that creates lexer and parser parser::NmodlDriver driver; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index ba3558e452..c3ea1f8ebc 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -17,6 +17,7 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # Common input data library # ============================================================================= add_library(test_util STATIC utils/nmodl_constructs.cpp utils/test_utils.cpp) +target_link_libraries(test_util PUBLIC spdlog::spdlog_header_only) # ============================================================================= # Common input data library diff --git a/test/unit/parser/parser.cpp b/test/unit/parser/parser.cpp index 0ab51a2bf7..42e9a7b511 100644 --- a/test/unit/parser/parser.cpp +++ b/test/unit/parser/parser.cpp @@ -134,7 +134,7 @@ SCENARIO("NMODL parser accepts empty unit specification") { } SCENARIO("NMODL parser running number of valid NMODL constructs") { - nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); + TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { @@ -167,12 +167,11 @@ SCENARIO("Check that the parser doesn't crash when passing invalid INCLUDE const GIVEN("An missing included file") { REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"unknown.file\""), - Catch::Contains("can not open file : unknown.file")); + Catch::Contains("can not open file : \"unknown.file\"")); } GIVEN("An invalid included file") { - nmodl::utils::TempFile included("included.file", - nmodl_invalid_constructs.at("title_1").input); + TempFile included("included.file", nmodl_invalid_constructs.at("title_1").input); REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"included.file\""), Catch::Contains("unexpected End of file")); } @@ -199,7 +198,7 @@ SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { SCENARIO("Check parents in valid NMODL constructs") { nmodl::parser::NmodlDriver driver; - nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); + TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { // parse the string and get the ast const auto ast = driver.parse_string(construct.second.input); diff --git a/test/unit/units/parser.cpp b/test/unit/units/parser.cpp index 2133d147a7..b3d9050af7 100644 --- a/test/unit/units/parser.cpp +++ b/test/unit/units/parser.cpp @@ -154,7 +154,7 @@ SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { THEN("parser multiply the number by the factor") { std::string parsed_unit{}; REQUIRE_NOTHROW(parsed_unit = parse_string("pew 1 1/milli")); - REQUIRE_THAT(parsed_unit, Contains("pew 0.00100000: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_unit, Contains("pew 0.001: 0 0 0 0 0 0 0 0 0 0")); } } } @@ -192,19 +192,19 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars R2 8314 mV-coul/degC )"; std::string parsed_units = parse_string(reindent_text(units_definitions)); - REQUIRE_THAT(parsed_units, Contains("mV 0.00100000: 2 1 -2 -1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("mM 1.00000000: -3 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("mA 0.00100000: 0 0 -1 1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("KTOMV 0.00008530: 2 1 -2 -1 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("B 26.00000000: -1 0 0 -1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy1 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy2 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy3 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy4 -0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy5 0.02500000: 0 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("R 8.31446262: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("R1 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("R2 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("mV 0.001: 2 1 -2 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("mM 1: -3 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("mA 0.001: 0 0 -1 1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("KTOMV 8.53e-05: 2 1 -2 -1 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("B 26: -1 0 0 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy1 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy2 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy3 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy4 -0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy5 0.025: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("R 8.31446: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("R1 8.314: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("R2 8.314: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE_THAT(parsed_units, Contains("m kg sec coul candela dollar bit erlang K")); } } diff --git a/test/unit/utils/test_utils.cpp b/test/unit/utils/test_utils.cpp index 7f6e81841b..82e2a364b7 100644 --- a/test/unit/utils/test_utils.cpp +++ b/test/unit/utils/test_utils.cpp @@ -5,9 +5,15 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "test_utils.hpp" +#include "utils/logger.hpp" #include "utils/string_utils.hpp" #include +#include +#include + +namespace fs = std::filesystem; namespace nmodl { namespace test_utils { @@ -80,5 +86,20 @@ std::string reindent_text(const std::string& text) { return indented_text; } +TempFile::TempFile(fs::path path, const std::string& content) + : path_(std::move(path)) { + std::ofstream output(path_); + output << content; +} + +TempFile::~TempFile() { + try { + fs::remove(path_); + } catch (...) { + // TODO: remove .string() once spdlog use fmt 9.1.0 + logger->error("Cannot delete temporary file {}", path_.string()); + } +} + } // namespace test_utils } // namespace nmodl diff --git a/test/unit/utils/test_utils.hpp b/test/unit/utils/test_utils.hpp index 75f3828b43..d2e0447b9e 100644 --- a/test/unit/utils/test_utils.hpp +++ b/test/unit/utils/test_utils.hpp @@ -7,10 +7,24 @@ #pragma once +#include +#include + namespace nmodl { namespace test_utils { std::string reindent_text(const std::string& text); +/** + * \brief Create an empty file which is then removed when the C++ object is destructed + */ +struct TempFile { + TempFile(std::filesystem::path path, const std::string& content); + ~TempFile(); + + private: + std::filesystem::path path_; +}; + } // namespace test_utils } // namespace nmodl diff --git a/test/unit/visitor/nmodl.cpp b/test/unit/visitor/nmodl.cpp index b2879f3d7c..263fae1f83 100644 --- a/test/unit/visitor/nmodl.cpp +++ b/test/unit/visitor/nmodl.cpp @@ -41,7 +41,7 @@ std::string run_nmodl_visitor(const std::string& text) { } SCENARIO("Convert AST back to NMODL form", "[visitor][nmodl]") { - nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); + TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; const std::string& input_nmodl_text = reindent_text(test_case.input);