File: parser.cpp

package info (click to toggle)
nmodl 0.6-3
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 5,992 kB
  • sloc: cpp: 28,492; javascript: 9,841; yacc: 2,804; python: 1,967; lex: 1,674; xml: 181; sh: 136; ansic: 37; makefile: 18; pascal: 7
file content (272 lines) | stat: -rw-r--r-- 10,070 bytes parent folder | download | duplicates (2)
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");
    }
}