User Guide
symbol_table.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <memory>
9 #include <string>
10 
11 #include <catch2/catch_test_macros.hpp>
12 #include <catch2/matchers/catch_matchers_string.hpp>
13 
14 #include "ast/float.hpp"
15 #include "ast/program.hpp"
16 #include "ast/string.hpp"
17 #include "symtab/symbol.hpp"
18 #include "symtab/symbol_table.hpp"
19 
20 using namespace nmodl;
21 using namespace symtab;
22 using namespace syminfo;
23 
24 //=============================================================================
25 // Symbol properties test
26 //=============================================================================
27 
28 SCENARIO("Symbol properties can be added and converted to string") {
32 
33  GIVEN("A empty property") {
34  WHEN("converted to string") {
35  THEN("returns empty string") {
36  REQUIRE(to_string(prop1).empty());
37  }
38  }
39  WHEN("checked for property") {
40  THEN("doesn't have any property") {
41  REQUIRE_FALSE(has_property(prop1, NmodlType::local_var));
42  }
43  }
44  WHEN("adding another empty property") {
45  NmodlType result = prop1 | prop1; // NOLINT(misc-redundant-expression)
46  THEN("to_string still returns empty string") {
47  REQUIRE(to_string(result).empty());
48  }
49  }
50  WHEN("added some other property") {
51  NmodlType result = prop1 | prop2;
52  THEN("to_string returns added property") {
53  REQUIRE(to_string(result) == "local");
54  }
55  WHEN("checked for property") {
56  THEN("has required property") {
57  REQUIRE(has_property(result, NmodlType::local_var) == true);
58  }
59  }
60  }
61  WHEN("added multiple properties") {
62  NmodlType result = prop1 | prop2 | prop3;
63  result |= NmodlType::write_ion_var;
64  THEN("to_string returns all added properties") {
65  REQUIRE_THAT(to_string(result), Catch::Matchers::ContainsSubstring("local"));
66  REQUIRE_THAT(to_string(result), Catch::Matchers::ContainsSubstring("global"));
67  REQUIRE_THAT(to_string(result), Catch::Matchers::ContainsSubstring("write_ion"));
68  }
69  WHEN("checked for property") {
70  THEN("has all added properties") {
71  REQUIRE(has_property(result, NmodlType::local_var) == true);
72  REQUIRE(has_property(result, NmodlType::global_var) == true);
73  REQUIRE(has_property(result, NmodlType::write_ion_var) == true);
74  REQUIRE_FALSE(has_property(result, NmodlType::read_ion_var));
75  }
76  }
77  }
78  WHEN("properties manipulated with bitwise operators") {
79  THEN("& applied correctly") {
80  NmodlType result1 = prop2 & prop2; // NOLINT(misc-redundant-expression)
81  NmodlType result2 = prop1 & prop2;
82  NmodlType result3 = prop1 & prop2 & prop3;
83  REQUIRE(to_string(result1) == "local");
84  REQUIRE(to_string_vector(result2).empty());
85  REQUIRE(result2 == NmodlType::empty);
86  REQUIRE(result3 == NmodlType::empty);
87  }
88  }
89  }
90 }
91 
92 //=============================================================================
93 // Symbol test
94 //=============================================================================
95 
96 SCENARIO("Multiple properties can be added to Symbol") {
97  NmodlType property1 = NmodlType::argument;
98  NmodlType property2 = NmodlType::range_var;
100  GIVEN("A symbol") {
101  ModToken token(true);
102  Symbol symbol("alpha", token);
103  WHEN("added external property") {
105  THEN("symbol becomes external") {
106  REQUIRE(symbol.is_external_variable() == true);
107  }
108  }
109  WHEN("added multiple properties to symbol") {
110  symbol.add_property(property1);
111  symbol.add_property(property2);
112  THEN("symbol has multiple properties") {
113  REQUIRE(symbol.has_any_property(property1) == true);
114 
115  REQUIRE(symbol.has_any_property(property3) == false);
116 
117  symbol.add_property(property3);
118  REQUIRE(symbol.has_any_property(property3) == true);
119 
120  auto property = property1 | property2;
121  REQUIRE(symbol.has_all_properties(property) == true);
122 
123  property |= property3;
124  REQUIRE(symbol.has_all_properties(property) == true);
125 
126  property = property2 | property3;
127  REQUIRE(symbol.has_all_properties(property) == true);
128 
129  property |= NmodlType::to_solve;
130  REQUIRE(symbol.has_all_properties(property) == false);
131  }
132  }
133  WHEN("remove properties from symbol") {
134  symbol.add_property(property1);
135  symbol.add_property(property2);
136  THEN("remove property that exists") {
137  REQUIRE(symbol.has_any_property(property2) == true);
138  symbol.remove_property(property2);
139  REQUIRE(symbol.has_any_property(property2) == false);
140  }
141  THEN("remove property that doesn't exist") {
142  REQUIRE(symbol.has_any_property(property3) == false);
143  symbol.remove_property(property3);
144  REQUIRE(symbol.has_any_property(property3) == false);
145  auto properties = property1 | property2;
146  REQUIRE(symbol.has_all_properties(properties) == true);
147  }
148  }
149  WHEN("combined properties") {
151  THEN("symbol has union of all properties") {
152  REQUIRE(symbol.has_any_property(property) == false);
153  symbol.add_properties(property);
154  REQUIRE(symbol.has_any_property(property) == true);
155  property |= symbol.get_properties();
156  REQUIRE(symbol.get_properties() == property);
157  }
158  }
159  }
160 }
161 
162 //=============================================================================
163 // Symbol table test
164 //=============================================================================
165 
166 SCENARIO("Symbol table allows operations like insert, lookup") {
167  GIVEN("A global SymbolTable") {
168  auto program = std::make_shared<ast::Program>();
169  auto table = std::make_shared<SymbolTable>("Na", program.get(), true);
170  auto symbol = std::make_shared<Symbol>("alpha");
171 
172  WHEN("checked methods and member variables") {
173  THEN("all members are initialized") {
174  REQUIRE(table->under_global_scope());
175  REQUIRE_THAT(table->name(), Catch::Matchers::ContainsSubstring("Na"));
176  REQUIRE_THAT(table->get_parent_table_name(),
177  Catch::Matchers::ContainsSubstring("None"));
178  REQUIRE_THAT(table->position(), Catch::Matchers::ContainsSubstring("UNKNOWN"));
179  }
180  }
181  WHEN("insert symbol") {
182  table->insert(symbol);
183  THEN("table size increases") {
184  REQUIRE(table->symbol_count() == 1);
185  }
186  THEN("lookup returns an inserted symbol") {
187  REQUIRE(table->lookup("alpha") != nullptr);
188  REQUIRE(table->lookup("beta") == nullptr);
189  }
190  WHEN("re-inserting the same symbol") {
191  THEN("throws an exception") {
192  REQUIRE_THROWS_WITH(table->insert(symbol),
193  Catch::Matchers::ContainsSubstring("re-insert"));
194  }
195  }
196  WHEN("inserting another symbol") {
197  auto next_symbol = std::make_shared<Symbol>("beta");
198  table->insert(next_symbol);
199  THEN("symbol gets added and table size increases") {
200  REQUIRE(table->symbol_count() == 2);
201  REQUIRE(table->lookup("beta") != nullptr);
202  }
203  }
204  }
205  WHEN("checked for global variables") {
206  table->insert(symbol);
207  auto variables = table->get_variables_with_properties(NmodlType::range_var);
208  THEN("table doesn't have any global variables") {
209  REQUIRE(variables.empty());
210  WHEN("added global symbol") {
211  auto next_symbol = std::make_shared<Symbol>("gamma");
212  next_symbol->add_property(NmodlType::assigned_definition);
213  table->insert(next_symbol);
214  auto variables = table->get_variables_with_properties(
216  THEN("table has global variable") {
217  REQUIRE(variables.size() == 1);
218  }
219  }
220  }
221  }
222  WHEN("added another symbol table as children") {
223  table->insert(symbol);
224  auto next_program = std::make_shared<ast::Program>();
225  auto next_table = std::make_shared<SymbolTable>("Ca", next_program.get(), true);
226  next_table->set_parent_table(table.get());
227  THEN("children symbol table can lookup into parent table scope") {
228  REQUIRE(next_table->lookup("alpha") == nullptr);
229  REQUIRE(next_table->lookup_in_scope("alpha") != nullptr);
230  }
231  THEN("children can figure if it is in global scope or not") {
232  REQUIRE(next_table->global_scope() == table->global_scope());
233  }
234  }
235  WHEN("pretty-printing a symbol table to a stream") {
236  std::ostringstream oss;
237  table->print(oss, 0);
238  auto text = oss.str();
239  THEN("nothing is written when the table is empty") {
240  REQUIRE(text.empty());
241  }
242  table->insert(symbol);
243  table->print(oss, 0);
244  text = oss.str();
245  THEN("the symbol present in the table can be found in the written string") {
246  REQUIRE(text.find(symbol->get_name()) != std::string::npos);
247  }
248  }
249  WHEN("creating a clone of symbol table") {
250  const std::unique_ptr<SymbolTable> clone(table->clone());
251  THEN("clone has the same name") {
252  REQUIRE(clone->name() == table->name());
253  }
254  }
255  WHEN("query for symbol with and without properties") {
256  auto symbol1 = std::make_shared<Symbol>("alpha");
257  auto symbol2 = std::make_shared<Symbol>("beta");
258  auto symbol3 = std::make_shared<Symbol>("gamma");
259  auto symbol4 = std::make_shared<Symbol>("delta");
260 
261  symbol1->add_property(NmodlType::range_var | NmodlType::param_assign);
262  symbol2->add_property(NmodlType::range_var | NmodlType::param_assign |
264  symbol3->add_property(NmodlType::range_var | NmodlType::assigned_definition |
266  symbol4->add_property(NmodlType::range_var);
267 
268  table->insert(symbol1);
269  table->insert(symbol2);
270  table->insert(symbol3);
271  table->insert(symbol4);
272 
273  auto result = table->get_variables_with_properties(NmodlType::range_var);
274  REQUIRE(result.size() == 4);
275 
276  result = table->get_variables_with_properties(NmodlType::range_var |
278  REQUIRE(result.size() == 4);
279 
282  result = table->get_variables(with, without);
283  REQUIRE(result.size() == 1);
284  REQUIRE(result[0]->get_name() == "alpha");
285 
286 
287  with = NmodlType::range_var;
289  result = table->get_variables(with, without);
290  REQUIRE(result.size() == 1);
291  REQUIRE(result[0]->get_name() == "delta");
292 
293  with = NmodlType::range_var;
294  without = NmodlType::range_var;
295  result = table->get_variables(with, without);
296  REQUIRE(result.empty());
297  }
298  }
299 }
300 
301 //=============================================================================
302 // Model symbol table test
303 //=============================================================================
304 
305 SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") {
306  GIVEN("A Model symbolTable") {
307  ModelSymbolTable mod_symtab;
308 
309  auto program = std::make_shared<ast::Program>();
310  auto symbol1 = std::make_shared<Symbol>("alpha");
311  auto symbol2 = std::make_shared<Symbol>("alpha");
312  auto symbol3 = std::make_shared<Symbol>("alpha");
313 
314  symbol1->add_property(NmodlType::param_assign);
315  symbol2->add_property(NmodlType::range_var);
316  symbol3->add_property(NmodlType::range_var);
317 
318  SymbolTable* old_symtab = nullptr;
319 
320  WHEN("trying to exit scope without entering") {
321  THEN("throws an exception") {
322  REQUIRE_THROWS_WITH(mod_symtab.leave_scope(),
323  Catch::Matchers::ContainsSubstring("without entering"));
324  }
325  }
326  WHEN("trying to enter scope without valid node") {
327  THEN("throws an exception") {
328  REQUIRE_THROWS_WITH(mod_symtab.enter_scope("scope", nullptr, true, old_symtab),
329  Catch::Matchers::ContainsSubstring("empty node"));
330  }
331  }
332  WHEN("trying to insert without entering scope") {
333  THEN("throws an exception") {
334  auto symbol = std::make_shared<Symbol>("alpha");
335  REQUIRE_THROWS_WITH(mod_symtab.insert(symbol),
336  Catch::Matchers::ContainsSubstring("Can not insert"));
337  }
338  }
339  WHEN("enter scope multiple times") {
340  auto program1 = std::make_shared<ast::Program>();
341  auto program2 = std::make_shared<ast::Program>();
342  mod_symtab.enter_scope("scope1", program1.get(), false, old_symtab);
343  mod_symtab.enter_scope("scope2", program2.get(), false, old_symtab);
344  THEN("can leave scope multiple times") {
345  mod_symtab.leave_scope();
346  mod_symtab.leave_scope();
347  }
348  }
349  WHEN("added same symbol with different properties in global scope") {
350  mod_symtab.enter_scope("scope", program.get(), true, old_symtab);
351  mod_symtab.insert(symbol1);
352  mod_symtab.insert(symbol2);
353  THEN("only one symbol gets added with combined properties") {
354  auto symbol = mod_symtab.lookup("alpha");
355  auto properties = NmodlType::param_assign | NmodlType::range_var;
356  REQUIRE(symbol->get_properties() == properties);
357  }
358  }
359  WHEN("added same symbol with existing property") {
360  mod_symtab.enter_scope("scope", program.get(), true, old_symtab);
361  mod_symtab.insert(symbol1);
362  mod_symtab.insert(symbol2);
363  THEN("throws an exception") {
364  REQUIRE_THROWS_WITH(mod_symtab.insert(symbol3),
365  Catch::Matchers::ContainsSubstring("Re-declaration"));
366  }
367  }
368  WHEN("added same symbol in children scope") {
369  mod_symtab.enter_scope("scope1", program.get(), true, old_symtab);
370  mod_symtab.insert(symbol2);
371  THEN("it's ok, just get overshadow warning") {
372  mod_symtab.enter_scope("scope2", program.get(), false, old_symtab);
373  mod_symtab.insert(symbol3);
374  ///\todo : not sure how to capture std::cout
375  }
376  }
377  }
378 }
379 
380 //=============================================================================
381 // Symbol class tests
382 //=============================================================================
383 
384 SCENARIO("Symbol class allows manipulation") {
385  GIVEN("A symbol can have several nodes") {
386  auto st = std::make_shared<ast::String>("node1");
387  auto fl = std::make_shared<ast::Float>("1.1");
388  Symbol symbol1("alpha");
389  symbol1.add_node(st.get());
390  symbol1.add_node(fl.get());
391 
392  Symbol symbol2("beta");
393 
394  WHEN("trying to get name") {
395  THEN("it works") {
396  REQUIRE(symbol1.get_name() == "alpha");
397  REQUIRE(symbol2.get_name() == "beta");
398  }
399  }
400 
401  WHEN("trying to get all nodes") {
402  THEN("it works") {
403  REQUIRE(symbol1.get_nodes().size() == 2);
404  REQUIRE(symbol2.get_nodes().empty());
405  }
406  }
407 
408  WHEN("trying to get specific node") {
409  auto nodes = symbol1.get_nodes_by_type({ast::AstNodeType::STRING});
410 
411  THEN("it works") {
412  REQUIRE(nodes.size() == 1);
413  REQUIRE(nodes.front()->is_string());
414  REQUIRE(symbol2.get_nodes_by_type({ast::AstNodeType::STRING}).empty());
415  }
416  }
417  WHEN("read and write counters works") {
418  symbol1.read();
419  symbol1.read();
420  symbol1.write();
421 
422  THEN("it works") {
423  REQUIRE(symbol1.get_read_count() == 2);
424  REQUIRE(symbol1.get_write_count() == 1);
425  REQUIRE(symbol2.get_read_count() == 0);
426  REQUIRE(symbol2.get_write_count() == 0);
427  }
428  }
429 
430  WHEN("renaming a symbol") {
431  symbol2.set_name("gamma");
432  THEN("get_name return the new name") {
433  REQUIRE(symbol2.get_name() == "gamma");
434  REQUIRE(symbol2.get_original_name() == "beta");
435  }
436  symbol2.set_original_name("gamma");
437  THEN("get_original_name return the new name") {
438  REQUIRE(symbol2.get_original_name() == "gamma");
439  }
440  }
441 
442  WHEN("set as array") {
443  symbol1.set_as_array(15);
444  THEN("recognized as an array") {
445  REQUIRE(symbol1.get_length() == 15);
446  REQUIRE(symbol1.is_array());
447 
448  REQUIRE(symbol2.get_length() == 1);
449  REQUIRE(!symbol2.is_array());
450  }
451  }
452  }
453 }
nmodl::symtab::Symbol::set_as_array
void set_as_array(int len) noexcept
Definition: symbol.hpp:189
SCENARIO
SCENARIO("Symbol properties can be added and converted to string")
Definition: symbol_table.cpp:28
nmodl::symtab::syminfo::NmodlType::write_ion_var
@ write_ion_var
Write Ion.
nmodl::symtab::Symbol::get_nodes
const std::vector< ast::Ast * > & get_nodes() const noexcept
Definition: symbol.hpp:247
symbol.hpp
Implement class to represent a symbol in Symbol Table.
nmodl::symtab::Symbol
Represent symbol in symbol table.
Definition: symbol.hpp:55
nmodl::symtab::ModelSymbolTable::insert
std::shared_ptr< Symbol > insert(const std::shared_ptr< Symbol > &symbol)
insert new symbol into current table
Definition: symbol_table.cpp:286
nmodl::codegen::get_name
std::string get_name(ast::Ast const *sym)
Definition: codegen_cpp_visitor.hpp:172
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
nmodl::symtab::SymbolTable::name
const std::string & name() const noexcept
Definition: symbol_table.hpp:170
symbol_table.hpp
Implement classes for representing symbol table at block and file scope.
nmodl::symtab::syminfo::NmodlType::to_solve
@ to_solve
need to solve : used in solve statement
nmodl::symtab::syminfo::NmodlType::local_var
@ local_var
Local Variable.
nmodl::symtab::ModelSymbolTable::leave_scope
void leave_scope()
leaving current nmodl block
Definition: symbol_table.cpp:420
nmodl::symtab::Symbol::add_node
void add_node(ast::Ast *node) noexcept
Definition: symbol.hpp:243
nmodl::symtab::Symbol::get_length
int get_length() const noexcept
Definition: symbol.hpp:207
nmodl::symtab::syminfo::NmodlType::range_var
@ range_var
Range Variable.
nmodl::symtab::Symbol::get_read_count
int get_read_count() const noexcept
Definition: symbol.hpp:258
nmodl::symtab::Symbol::get_properties
const syminfo::NmodlType & get_properties() const noexcept
Definition: symbol.hpp:235
string.hpp
Auto generated AST classes declaration.
nmodl::symtab::Symbol::is_array
bool is_array() const noexcept
Definition: symbol.hpp:354
float.hpp
Auto generated AST classes declaration.
program.hpp
Auto generated AST classes declaration.
nmodl::symtab::Symbol::has_any_property
bool has_any_property(syminfo::NmodlType new_properties) const noexcept
check if symbol has any of the given property
Definition: symbol.hpp:289
nmodl::symtab::syminfo::NmodlType::argument
@ argument
Argument Type.
nmodl::symtab::syminfo::NmodlType::factor_def
@ factor_def
factor in unit block
nmodl::symtab::Symbol::get_name
const std::string & get_name() const noexcept
Definition: symbol.hpp:223
nmodl::symtab::syminfo::to_string
std::string to_string(const T &obj)
Definition: symbol_properties.hpp:282
nmodl::symtab::Symbol::add_property
void add_property(syminfo::NmodlType property) noexcept
add new property to symbol
Definition: symbol.hpp:314
nmodl::ast::AstNodeType::STRING
@ STRING
type of ast::String
nmodl::symtab::ModelSymbolTable::lookup
std::shared_ptr< Symbol > lookup(const std::string &name)
lookup for symbol into current as well as all parent tables
Definition: symbol_table.cpp:172
nmodl::symtab::syminfo::NmodlType::empty
@ empty
nmodl::symtab::SymbolTable
Represent symbol table for a NMODL block.
Definition: symbol_table.hpp:57
nmodl::symtab::Symbol::is_external_variable
bool is_external_variable() const noexcept
Check if symbol represent an external variable.
Definition: symbol.hpp:283
nmodl::symtab::ModelSymbolTable::enter_scope
SymbolTable * enter_scope(const std::string &name, ast::Ast *node, bool global, SymbolTable *node_symtab)
entering into new nmodl block
Definition: symbol_table.cpp:377
nmodl::symtab::syminfo::NmodlType::assigned_definition
@ assigned_definition
Assigned Definition.
nmodl::symtab::syminfo::NmodlType::param_assign
@ param_assign
Parameter Variable.
nmodl::symtab::Symbol::read
void read() noexcept
increment read count
Definition: symbol.hpp:135
nmodl::symtab::syminfo::NmodlType
NmodlType
NMODL variable properties.
Definition: symbol_properties.hpp:116
nmodl::symtab::Symbol::add_properties
void add_properties(syminfo::NmodlType new_properties) noexcept
add new properties to symbol
Definition: symbol.hpp:309
nmodl::symtab::Symbol::get_write_count
int get_write_count() const noexcept
Definition: symbol.hpp:262
nmodl::symtab::syminfo::NmodlType::pointer_var
@ pointer_var
Pointer Type.
nmodl::symtab::Symbol::remove_property
void remove_property(syminfo::NmodlType property)
remove property from symbol
Definition: symbol.hpp:319
nmodl::symtab::ModelSymbolTable
Hold top level (i.e.
Definition: symbol_table.hpp:239
nmodl::symtab::syminfo::NmodlType::read_ion_var
@ read_ion_var
Read Ion.
nmodl::symtab::syminfo::NmodlType::state_var
@ state_var
state variable
nmodl::symtab::Symbol::get_nodes_by_type
std::vector< ast::Ast * > get_nodes_by_type(std::initializer_list< ast::AstNodeType > l) const noexcept
Definition: symbol.cpp:52
nmodl::symtab::syminfo::Status::empty
@ empty
nmodl::symtab::syminfo::NmodlType::global_var
@ global_var
Global Variable.
nmodl::symtab::syminfo::to_string_vector
std::vector< std::string > to_string_vector(const NmodlType &obj)
helper function to convert nmodl properties to string
Definition: symbol_properties.cpp:23
nmodl::symtab::Symbol::write
void write() noexcept
increment write count
Definition: symbol.hpp:140
nmodl::symtab::Symbol::set_original_name
void set_original_name(const std::string &new_name)
Definition: symbol.hpp:198
nmodl::symtab::Symbol::get_original_name
const std::string & get_original_name() const noexcept
Definition: symbol.hpp:215
nmodl::symtab::Symbol::has_all_properties
bool has_all_properties(syminfo::NmodlType new_properties) const noexcept
check if symbol has all of the given properties
Definition: symbol.hpp:294
nmodl::ModToken
Represent token returned by scanner.
Definition: modtoken.hpp:50
nmodl::symtab::syminfo::NmodlType::discrete_block
@ discrete_block
Discrete Block.
nmodl::symtab::Symbol::set_name
void set_name(const std::string &new_name)
Set new name for the symbol.
Definition: symbol.hpp:162
nmodl::symtab::syminfo::NmodlType::extern_neuron_variable
@ extern_neuron_variable
neuron variable accessible in mod file
nmodl::symtab::syminfo::has_property
bool has_property(const NmodlType &obj, NmodlType property)
check if any property is set
Definition: symbol_properties.hpp:263