NEURON
merge_top_level_blocks.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2025 EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
9 #include "ast/initial_block.hpp"
10 #include "ast/program.hpp"
11 #include "ast/statement_block.hpp"
12 #include "parser/nmodl_driver.hpp"
13 #include "utils/test_utils.hpp"
19 
20 #include <catch2/catch_test_macros.hpp>
21 #include <catch2/matchers/catch_matchers_string.hpp>
22 
23 using namespace nmodl;
25 
26 using Catch::Matchers::ContainsSubstring; // ContainsSubstring in newer Catch2
27 
28 //=============================================================================
29 // MergeToplevelBlocks visitor tests
30 //=============================================================================
31 
32 template <typename block_type, ast::AstNodeType node_type>
35  auto ast = driver.parse_string(text);
37  return to_nmodl(*ast);
38 }
39 
40 SCENARIO("Check multiple INITIAL blocks are handled properly",
41  "[visitor][merge_top_level_blocks]") {
42  GIVEN("A mod file with multiple empty top-level INITIAL blocks") {
43  const auto nmodl_text_before = R"(
44  NEURON {
45  SUFFIX InitialBlockTest
46  RANGE foo, bar
47  }
48  INITIAL {
49  }
50  INITIAL {
51  }
52  INITIAL {
53  }
54  )";
55  const auto nmodl_text_after = R"(
56  NEURON {
57  SUFFIX InitialBlockTest
58  RANGE foo, bar
59  }
60  INITIAL {
61  }
62  )";
64  auto ast_expected = driver.parse_string(nmodl_text_after);
65  const auto program_expected = to_nmodl(ast_expected);
66  const auto program_actual =
69  nmodl_text_before);
70  THEN("expected and actual should be identical at the level of the AST") {
71  REQUIRE(reindent_text(program_actual) == reindent_text(program_expected));
72  }
73  }
74  GIVEN("A mod file with an empty and a non-empty top-level INITIAL block") {
75  const auto nmodl_text_before = R"(
76  NEURON {
77  SUFFIX InitialBlockTest
78  RANGE foo, bar
79  }
80  INITIAL {
81  }
82  INITIAL {
83  foo = 1
84  }
85  )";
86  const auto nmodl_text_after = R"(
87  NEURON {
88  SUFFIX InitialBlockTest
89  RANGE foo, bar
90  }
91  INITIAL {
92  {
93  foo = 1
94  }
95  }
96  )";
98  auto ast_expected = driver.parse_string(nmodl_text_after);
99  const auto program_expected = to_nmodl(ast_expected);
100  const auto program_actual =
103  nmodl_text_before);
104  THEN("expected and actual should be identical at the level of the AST") {
105  REQUIRE(reindent_text(program_actual) == reindent_text(program_expected));
106  }
107  }
108  GIVEN("A mod file with multiple non-empty INITIAL blocks") {
109  const auto nmodl_text_before = R"(
110  NEURON {
111  SUFFIX InitialBlockTest
112  RANGE foo, bar
113  }
114  INITIAL {
115  foo = 1
116  }
117  INITIAL {
118  bar = 2
119  }
120  )";
121  const auto nmodl_text_after = R"(
122  NEURON {
123  SUFFIX InitialBlockTest
124  RANGE foo, bar
125  }
126  INITIAL {
127  {
128  foo = 1
129  }
130  {
131  bar = 2
132  }
133  }
134  )";
136  auto ast_expected = driver.parse_string(nmodl_text_after);
137  const auto program_expected = to_nmodl(ast_expected);
138  const auto program_actual =
141  nmodl_text_before);
142  THEN("expected and actual should be identical at the level of the AST") {
143  REQUIRE(reindent_text(program_actual) == reindent_text(program_expected));
144  }
145  }
146  GIVEN("A mod file with an INITIAL block only inside of a NET_RECEIVE block") {
147  const auto nmodl_text_before = R"(
148  NEURON {
149  SUFFIX test
150  RANGE foo, bar
151  }
152 
153  NET_RECEIVE (w) {
154  INITIAL {
155  foo = 1
156  }
157  }
158  )";
159  const auto program_actual =
162  nmodl_text_before);
163  THEN("leave the mod file as-is") {
164  REQUIRE(reindent_text(program_actual) == reindent_text(nmodl_text_before));
165  }
166  }
167  GIVEN("A mod file with an INITIAL block, and one inside of a NET_RECEIVE block") {
168  // Note that the visitor actually modifies the AST (since there is > 1 INITIAL block in the
169  // entire file: one top-level, and one in NET_RECEIVE). If we place the top-level INITIAL
170  // block before NET_RECEIVE in the below, the top-level INITIAL block will be deleted and
171  // appended. However, since the position of the INITIAL block in the mod file has no impact
172  // on the semantics, the visitor works as expected
173  const auto nmodl_text_before = R"(
174  NEURON {
175  SUFFIX test
176  RANGE foo, bar
177  }
178 
179  NET_RECEIVE (w) {
180  INITIAL {
181  foo = 1
182  }
183  }
184 
185  INITIAL {
186  bar = 2
187  }
188  )";
189  const auto nmodl_text_after = R"(
190  NEURON {
191  SUFFIX test
192  RANGE foo, bar
193  }
194 
195  NET_RECEIVE (w) {
196  INITIAL {
197  foo = 1
198  }
199  }
200 
201  INITIAL {
202  {
203  bar = 2
204  }
205  }
206  )";
208  auto ast_expected = driver.parse_string(nmodl_text_after);
209  const auto program_expected = to_nmodl(ast_expected);
210  const auto program_actual =
213  nmodl_text_before);
214  THEN("leave the mod file as-is") {
215  REQUIRE(reindent_text(program_actual) == reindent_text(program_expected));
216  }
217  }
218 }
219 
220 SCENARIO("Check multiple BREAKPOINT blocks are handled properly",
221  "[visitor][merge_top_level_blocks]") {
222  GIVEN("A mod file with multiple BREAKPOINT blocks") {
223  const auto nmodl_text_before = R"(
224  NEURON {
225  SUFFIX BreakpointBlockTest
226  RANGE foo, bar
227  }
228  BREAKPOINT {
229  foo = 1
230  }
231  BREAKPOINT {
232  bar = 2
233  }
234  )";
235  const auto nmodl_text_after = R"(
236  NEURON {
237  SUFFIX BreakpointBlockTest
238  RANGE foo, bar
239  }
240  BREAKPOINT {
241  {
242  foo = 1
243  }
244  {
245  bar = 2
246  }
247  }
248  )";
250  auto ast_expected = driver.parse_string(nmodl_text_after);
251  const auto program_expected = to_nmodl(ast_expected);
252  const auto program_actual =
255  nmodl_text_before);
256  THEN("expected and actual should be identical at the level of the AST") {
257  REQUIRE(reindent_text(program_actual) == reindent_text(program_expected));
258  }
259  }
260 }
Auto generated AST classes declaration.
Represents a BREAKPOINT block in NMODL.
Represents a INITIAL block in the NMODL.
Class that binds all pieces together for parsing nmodl file.
Visitor which merges given top-level blocks into one.
void visit_program(ast::Program &node) override
visit node of type ast::Program
@ BREAKPOINT_BLOCK
type of ast::BreakpointBlock
@ INITIAL_BLOCK
type of ast::InitialBlock
bool parse_string(const std::string &input)
parser Units provided as string (used for testing)
Definition: unit_driver.cpp:40
Auto generated AST classes declaration.
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
SCENARIO("Check multiple INITIAL blocks are handled properly", "[visitor][merge_top_level_blocks]")
auto generate_mod_after_merge_top_level_blocks_visitor(std::string const &text)
Visitor which merges given top-level blocks into one.
std::string reindent_text(const std::string &text, int indent_level)
Reindent nmodl text for text-to-text comparison.
Definition: test_utils.cpp:55
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
std::string to_nmodl(const ast::Ast &node, const std::set< ast::AstNodeType > &exclude_types)
Given AST node, return the NMODL string representation.
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
#define text
Definition: plot.cpp:60
Auto generated AST classes declaration.
Auto generated AST classes declaration.
THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED.
nmodl::parser::UnitDriver driver
Definition: parser.cpp:28
Utility functions for visitors implementation.