User Guide
parser.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 <string>
9 #include <utility>
10 
11 #include <catch2/catch_test_macros.hpp>
12 #include <catch2/matchers/catch_matchers_string.hpp>
13 
14 #include "config/config.h"
15 #include "parser/diffeq_driver.hpp"
16 #include "parser/unit_driver.hpp"
18 
19 //=============================================================================
20 // Parser tests
21 //=============================================================================
22 
23 using namespace nmodl::test_utils;
24 using Catch::Matchers::ContainsSubstring;
25 
26 // Driver is defined as global to store all the units inserted to it and to be
27 // able to define complex units based on base units
28 nmodl::parser::UnitDriver driver; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
29 
30 namespace {
31 bool is_valid_construct(const std::string& construct) {
32  return driver.parse_string(construct);
33 }
34 
35 std::string parse_string(const std::string& unit_definition) {
36  nmodl::parser::UnitDriver correctness_driver;
37  correctness_driver.parse_file(nmodl::NrnUnitsLib::get_path());
38  correctness_driver.parse_string(unit_definition);
39  std::stringstream ss;
40  correctness_driver.table->print_units_sorted(ss);
41  correctness_driver.table->print_base_units(ss);
42  return ss.str();
43 }
44 } // namespace
45 
46 SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") {
47  GIVEN("A base unit") {
48  WHEN("Base unit is *a*") {
49  THEN("parser accepts without an error") {
50  REQUIRE(is_valid_construct("m *a*\n"));
51  }
52  }
53  WHEN("Base unit is *b*") {
54  THEN("parser accepts without an error") {
55  REQUIRE(is_valid_construct("kg\t\t\t*b*\n"));
56  }
57  }
58  WHEN("Base unit is *d*") {
59  THEN("parser accepts without an error") {
60  REQUIRE(is_valid_construct("coul\t\t\t*d*\n"));
61  }
62  }
63  WHEN("Base unit is *i*") {
64  THEN("parser accepts without an error") {
65  REQUIRE(is_valid_construct("erlang\t\t\t*i*\n"));
66  }
67  }
68  WHEN("Base unit is *c*") {
69  THEN("parser accepts without an error") {
70  REQUIRE(is_valid_construct("sec\t\t\t*c*\n"));
71  }
72  }
73  }
74  GIVEN("A double number") {
75  WHEN("Double number is writen like 3.14") {
76  THEN("parser accepts without an error") {
77  REQUIRE(is_valid_construct("pi\t\t\t3.14159265358979323846\n"));
78  }
79  }
80  WHEN("Double number is writen like 1") {
81  THEN("parser accepts without an error") {
82  REQUIRE(is_valid_construct("fuzz\t\t\t1\n"));
83  }
84  }
85  WHEN("Double number is negative") {
86  THEN("parser accepts without an error") {
87  REQUIRE(is_valid_construct("ckan\t\t\t-32.19976 m\n"));
88  }
89  }
90  }
91  GIVEN("A dimensionless constant") {
92  WHEN("Constant expression is double / constant") {
93  THEN("parser accepts without an error") {
94  REQUIRE(is_valid_construct("radian\t\t\t.5 / pi\n"));
95  }
96  }
97  }
98  GIVEN("A power of another unit") {
99  WHEN("Power of 2") {
100  THEN("parser accepts without an error") {
101  REQUIRE(is_valid_construct("steradian\t\tradian2\n"));
102  }
103  }
104  WHEN("Power of 3") {
105  THEN("parser accepts without an error") {
106  REQUIRE(is_valid_construct("stere\t\t\tm3\n"));
107  }
108  }
109  }
110  GIVEN("Divisions and multiplications of units") {
111  WHEN("Units are multiplied") {
112  THEN("parser accepts without an error") {
113  REQUIRE(is_valid_construct("degree\t\t\t1|180 pi-radian\n"));
114  }
115  }
116  WHEN("There are both divisions and multiplications") {
117  THEN("parser accepts without an error") {
118  REQUIRE(is_valid_construct("newton\t\t\tkg-m/sec2\n"));
119  }
120  }
121  WHEN("There is only division") {
122  THEN("parser accepts without an error") {
123  REQUIRE(is_valid_construct("dipotre\t\t\t/m\n"));
124  }
125  }
126  WHEN("Nominator is unknown") {
127  THEN("it throws") {
128  REQUIRE_THROWS(parse_string("foo 1 pew/m\n"));
129  }
130  }
131  WHEN("Denominator is unknown") {
132  THEN("it throws") {
133  REQUIRE_THROWS(parse_string("foo 1 m/pew\n"));
134  }
135  }
136  }
137  GIVEN("A double number and some units") {
138  WHEN("Double number is multiplied by a power of 10 with division of multiple units") {
139  THEN("parser accepts without an error") {
140  REQUIRE(is_valid_construct("c\t\t\t2.99792458+8 m/sec fuzz\n"));
141  }
142  }
143  WHEN("Double number is writen like .9") {
144  THEN("parser accepts without an error") {
145  REQUIRE(is_valid_construct("grade\t\t\t.9 degree\n"));
146  }
147  }
148  WHEN("A 's' is added") {
149  THEN("parser remove it to find the units") {
150  REQUIRE_NOTHROW(parse_string("pew 1 m\nfoo 2 pews\n"));
151  REQUIRE_NOTHROW(parse_string("pew 1 m\nfoo 2 /pews\n"));
152  }
153  }
154  WHEN("No unit but only a prefix factor") {
155  THEN("parser multiply the number by the factor") {
156  std::string parsed_unit{};
157  REQUIRE_NOTHROW(parsed_unit = parse_string("pew 1 1/milli"));
158  REQUIRE_THAT(parsed_unit, ContainsSubstring("pew 0.001: 0 0 0 0 0 0 0 0 0 0"));
159  }
160  }
161  }
162  GIVEN("A fraction and some units") {
163  WHEN("Fraction is writen like 1|2") {
164  THEN("parser accepts without an error") {
165  REQUIRE(is_valid_construct("ccs\t\t\t1|36 erlang\n"));
166  }
167  }
168  WHEN("Fraction is writen like 1|8.988e9") {
169  THEN("parser accepts without an error") {
170  REQUIRE(is_valid_construct("statcoul\t\t1|2.99792458+9 coul\n"));
171  }
172  }
173  }
174 }
175 
176 SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][parser]") {
177  GIVEN("Parsed the nrnunits.lib file") {
178  WHEN("Multiple units definitions based on the units defined in nrnunits.lib") {
179  THEN("parser accepts the units correctly") {
180  std::string units_definitions = R"(
181  mV millivolt
182  mM milli/liter
183  mA milliamp
184  KTOMV 0.0853 mV/degC
185  B 0.26 mM-cm2/mA-ms
186  dummy1 .025 1/m2
187  dummy2 1|4e+1 m/m3
188  dummy3 25-3 / m2
189  dummy4 -0.025 /m2
190  dummy5 2.5 %
191  newR k-mole
192  R1 8.314 volt-coul/degC
193  R2 8314 mV-coul/degC
194  )";
195  std::string parsed_units = parse_string(reindent_text(units_definitions));
196  REQUIRE_THAT(parsed_units, ContainsSubstring("mV 0.001: 2 1 -2 -1 0 0 0 0 0 0"));
197  REQUIRE_THAT(parsed_units, ContainsSubstring("mM 1: -3 0 0 0 0 0 0 0 0 0"));
198  REQUIRE_THAT(parsed_units, ContainsSubstring("mA 0.001: 0 0 -1 1 0 0 0 0 0 0"));
199  REQUIRE_THAT(parsed_units,
200  ContainsSubstring("KTOMV 8.53e-05: 2 1 -2 -1 0 0 0 0 0 -1"));
201  REQUIRE_THAT(parsed_units, ContainsSubstring("B 26: -1 0 0 -1 0 0 0 0 0 0"));
202  REQUIRE_THAT(parsed_units, ContainsSubstring("dummy1 0.025: -2 0 0 0 0 0 0 0 0 0"));
203  REQUIRE_THAT(parsed_units, ContainsSubstring("dummy2 0.025: -2 0 0 0 0 0 0 0 0 0"));
204  REQUIRE_THAT(parsed_units, ContainsSubstring("dummy3 0.025: -2 0 0 0 0 0 0 0 0 0"));
205  REQUIRE_THAT(parsed_units,
206  ContainsSubstring("dummy4 -0.025: -2 0 0 0 0 0 0 0 0 0"));
207  REQUIRE_THAT(parsed_units, ContainsSubstring("dummy5 0.025: 0 0 0 0 0 0 0 0 0 0"));
208  REQUIRE_THAT(parsed_units,
209  ContainsSubstring("newR 8.31446: 2 1 -2 0 0 0 0 0 0 -1"));
210  REQUIRE_THAT(parsed_units, ContainsSubstring("R1 8.314: 2 1 -2 0 0 0 0 0 0 -1"));
211  REQUIRE_THAT(parsed_units, ContainsSubstring("R2 8.314: 2 1 -2 0 0 0 0 0 0 -1"));
212  REQUIRE_THAT(parsed_units,
213  ContainsSubstring("m kg sec coul candela dollar bit erlang K"));
214  }
215  }
216  }
217 }
nmodl::parser::UnitDriver::table
std::shared_ptr< nmodl::units::UnitTable > table
shared pointer to the UnitTable that stores all the unit definitions
Definition: unit_driver.hpp:52
test_utils.hpp
nmodl::test_utils::reindent_text
std::string reindent_text(const std::string &text, int indent_level)
Reindent nmodl text for text-to-text comparison.
Definition: test_utils.cpp:53
unit_driver.hpp
SCENARIO
SCENARIO("NMODL can accept CR as return char for one line comment", "[parser]")
Definition: parser.cpp:36
nmodl::test_utils
custom type to represent nmodl construct for testing
Definition: nmodl_constructs.cpp:12
driver
nmodl::parser::UnitDriver driver
Definition: parser.cpp:28
nmodl::parser::UnitDriver::parse_string
bool parse_string(const std::string &input)
parser Units provided as string (used for testing)
Definition: unit_driver.cpp:40
diffeq_driver.hpp
is_valid_construct
bool is_valid_construct(const std::string &construct)
Definition: parser.cpp:30
config.h
Version information and units file path.
nmodl::parser::UnitDriver
Class that binds all pieces together for parsing C units.
Definition: unit_driver.hpp:39
nmodl::NrnUnitsLib::get_path
static std::string get_path()
Return path of units database file.
Definition: config.h:54
nmodl::parser::UnitDriver::parse_file
bool parse_file(const std::string &filename)
parse Units file
Definition: unit_driver.cpp:29