1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
|
/*************************************************************************
* Copyright (C) 2018-2022 Blue Brain Project
*
* This file is part of NMODL distributed under the terms of the GNU
* Lesser General Public License. See top-level LICENSE file for details.
*************************************************************************/
#include <string>
#include <utility>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include "ast/program.hpp"
#include "lexer/modtoken.hpp"
#include "parser/diffeq_driver.hpp"
#include "parser/nmodl_driver.hpp"
#include "test/unit/utils/nmodl_constructs.hpp"
#include "test/unit/utils/test_utils.hpp"
#include "utils/common_utils.hpp"
#include "visitors/checkparent_visitor.hpp"
#include "visitors/visitor_utils.hpp"
using namespace nmodl::test_utils;
//=============================================================================
// Parser tests
//=============================================================================
bool is_valid_construct(const std::string& construct) {
nmodl::parser::NmodlDriver driver;
return driver.parse_string(construct) != nullptr;
}
SCENARIO("NMODL can accept CR as return char for one line comment", "[parser]") {
GIVEN("A comment defined with CR as return char") {
WHEN("parsing") {
THEN("success") {
REQUIRE(is_valid_construct(R"(: see you next line
PROCEDURE foo() {
})"));
}
}
}
}
SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") {
GIVEN("A valid macro definition") {
WHEN("DEFINE NSTEP 6") {
THEN("parser accepts without an error") {
REQUIRE(is_valid_construct("DEFINE NSTEP 6"));
}
}
WHEN("DEFINE NSTEP 6") {
THEN("parser accepts without an error") {
REQUIRE(is_valid_construct("DEFINE NSTEP 6"));
}
}
}
GIVEN("A macro with nested definition is not supported") {
WHEN("DEFINE SIX 6 DEFINE NSTEP SIX") {
THEN("parser throws an error") {
REQUIRE_THROWS_WITH(is_valid_construct("DEFINE SIX 6 DEFINE NSTEP SIX"),
Catch::Matchers::ContainsSubstring("unexpected INVALID_TOKEN"));
}
}
}
GIVEN("A invalid macro definition with float value") {
WHEN("DEFINE NSTEP 6.0") {
THEN("parser throws an exception") {
REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP 6.0"),
Catch::Matchers::ContainsSubstring("unexpected REAL"));
}
}
}
GIVEN("A invalid macro definition with name and without value") {
WHEN("DEFINE NSTEP") {
THEN("parser throws an exception") {
REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP"),
Catch::Matchers::ContainsSubstring("expecting INTEGER"));
}
}
}
GIVEN("A invalid macro definition with name and value as a name") {
WHEN("DEFINE NSTEP SIX") {
THEN("parser throws an exception") {
REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP SIX"),
Catch::Matchers::ContainsSubstring("expecting INTEGER"));
}
}
}
GIVEN("A invalid macro definition without name but with value") {
WHEN("DEFINE 6") {
THEN("parser throws an exception") {
REQUIRE_THROWS_WITH(is_valid_construct("DEFINE 6"),
Catch::Matchers::ContainsSubstring("expecting NAME"));
}
}
}
}
SCENARIO("Macros can be used anywhere in the mod file") {
std::string nmodl_text = R"(
DEFINE NSTEP 6
PARAMETER {
amp[NSTEP] (mV)
}
)";
WHEN("macro is used in parameter definition") {
THEN("parser accepts without an error") {
REQUIRE(is_valid_construct(nmodl_text));
}
}
}
SCENARIO("NMODL parser accepts empty unit specification") {
std::string nmodl_text = R"(
FUNCTION ssCB(kdf(), kds()) (mM) {
}
)";
WHEN("FUNCTION is defined with empty unit") {
THEN("parser accepts without an error") {
REQUIRE(is_valid_construct(nmodl_text));
}
}
}
SCENARIO("NMODL parser running number of valid NMODL constructs") {
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) {
THEN("Parser successfully parses : " + test_case.input) {
REQUIRE(is_valid_construct(test_case.input));
}
}
}
}
SCENARIO("NMODL parser running number of invalid NMODL constructs") {
for (const auto& construct: nmodl_invalid_constructs) {
auto test_case = construct.second;
GIVEN(test_case.name) {
THEN("Parser throws an exception while parsing : " + test_case.input) {
REQUIRE_THROWS(is_valid_construct(test_case.input));
}
}
}
}
//=============================================================================
// Ensure that the parser can handle invalid INCLUDE constructs
//=============================================================================
SCENARIO("Check that the parser doesn't crash when passing invalid INCLUDE constructs") {
GIVEN("An empty filename") {
REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"\""),
Catch::Matchers::ContainsSubstring("empty filename"));
}
GIVEN("An missing included file") {
REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"unknown.file\""),
Catch::Matchers::ContainsSubstring(
"can not open file : \"unknown.file\""));
}
GIVEN("An invalid included file") {
TempFile included("included.file", nmodl_invalid_constructs.at("title_1").input);
REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"included.file\""),
Catch::Matchers::ContainsSubstring("unexpected End of file"));
}
}
SCENARIO("NEURON block can add CURIE information", "[parser][represents]") {
GIVEN("A valid CURIE information statement") {
THEN("parser accepts without an error") {
REQUIRE(is_valid_construct("NEURON { REPRESENTS NCIT:C17008 }"));
REQUIRE(is_valid_construct("NEURON { REPRESENTS [NCIT:C17008] }"));
}
}
GIVEN("Incomplete CURIE information statement") {
THEN("parser throws an error") {
REQUIRE_THROWS_WITH(is_valid_construct("NEURON { REPRESENTS }"),
Catch::Matchers::ContainsSubstring("Lexer Error"));
REQUIRE_THROWS_WITH(is_valid_construct("NEURON { REPRESENTS NCIT}"),
Catch::Matchers::ContainsSubstring("Lexer Error"));
}
}
}
SCENARIO("Check parents in valid NMODL constructs") {
nmodl::parser::NmodlDriver driver;
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);
GIVEN(construct.second.name) {
THEN("Check the parents in : " + construct.second.input) {
// check the parents
REQUIRE(!nmodl::visitor::test::CheckParentVisitor().check_ast(*ast));
}
}
}
}
//=============================================================================
// Differential Equation Parser tests
//=============================================================================
std::string solve_construct(const std::string& equation, std::string method) {
auto solution = nmodl::parser::DiffeqDriver::solve(equation, std::move(method));
return solution;
}
SCENARIO("Legacy differential equation solver") {
GIVEN("A differential equation") {
int counter = 0;
for (const auto& test_case: diff_eq_constructs) {
auto prefix = "." + std::to_string(counter);
WHEN(prefix + " EQUATION = " + test_case.equation + " METHOD = " + test_case.method) {
THEN(prefix + " SOLUTION = " + test_case.solution) {
auto expected_result = test_case.solution;
auto result = solve_construct(test_case.equation, test_case.method);
REQUIRE(result == expected_result);
}
}
counter++;
}
}
}
//=============================================================================
// Check if parsed NEURON block has correct token information
//=============================================================================
void parse_neuron_block_string(const std::string& name, nmodl::ModToken& value) {
nmodl::parser::NmodlDriver driver;
driver.parse_string(name);
const auto& ast_program = driver.get_ast();
const auto& neuron_blocks = nmodl::collect_nodes(*ast_program->get_shared_ptr(),
{nmodl::ast::AstNodeType::NEURON_BLOCK});
value = *(neuron_blocks.front()->get_token());
}
SCENARIO("Check if a NEURON block is parsed with correct location info in its token") {
GIVEN("A single NEURON block") {
nmodl::ModToken value;
std::stringstream ss;
std::string neuron_block = R"(
NEURON {
SUFFIX it
USEION ca READ cai,cao WRITE ica
RANGE gcabar, m_inf, tau_m, alph1, alph2, KK, shift
}
)";
parse_neuron_block_string(reindent_text(neuron_block), value);
ss << value;
REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 296");
}
}
|