diff --git a/modcc/expression.hpp b/modcc/expression.hpp index b2eb6e463c254a7d5182fec4e55ad6f9e36c9e8e..a9f752ec382fbac4df6ff83000bdb03532a3eb4b 100644 --- a/modcc/expression.hpp +++ b/modcc/expression.hpp @@ -877,7 +877,7 @@ public: {} std::string to_string() const override { - return pprintf("%%", coeff()->to_string(), ident()->to_string()); + return pprintf("% %", coeff()->to_string(), ident()->to_string()); } void semantic(std::shared_ptr<scope_type> scp) override; expression_ptr clone() const override; diff --git a/modcc/lexer.cpp b/modcc/lexer.cpp index 74da1a07eed937ef20d94f0df4f26a1a34dc960b..2528f19297c38be9330e5c2b4cbed7d04a6ffe05 100644 --- a/modcc/lexer.cpp +++ b/modcc/lexer.cpp @@ -119,6 +119,10 @@ Token Lexer::parse() { t.type = tok::rbrace; t.spelling += character(); return t; + case '~': + t.type = tok::tilde; + t.spelling += character(); + return t; case '=': { t.spelling += character(); if(*current_=='=') { diff --git a/modcc/parser.cpp b/modcc/parser.cpp index 74692fc91c4f32670e90011ed103d06ac6561070..173da807b6287969c18874230725b0d2f99582f1 100644 --- a/modcc/parser.cpp +++ b/modcc/parser.cpp @@ -967,11 +967,18 @@ expression_ptr Parser::parse_stoich_expression() { auto here = location_; if(token_.type==tok::integer || token_.type==tok::identifier) { - terms.push_back(parse_stoich_term()); + auto term = parse_stoich_term(); + if (!term) return nullptr; + + terms.push_back(std::move(term)); while(token_.type==tok::plus) { get_token(); // consume plus - terms.push_back(parse_stoich_term()); + + auto term = parse_stoich_term(); + if (!term) return nullptr; + + terms.push_back(std::move(term)); } } @@ -981,37 +988,48 @@ expression_ptr Parser::parse_stoich_expression() { expression_ptr Parser::parse_reaction_expression() { auto here = location_; - // consume tilde - get_token(); + if(token_.type!=tok::tilde) { + error(pprintf("expected '%', found '%'", yellow("~"), yellow(token_.spelling))); + return nullptr; + } + get_token(); // consume tilde expression_ptr lhs = parse_stoich_expression(); + if (!lhs) return nullptr; if(token_.type != tok::arrow) { error(pprintf("expected '%', found '%'", yellow("<->"), yellow(token_.spelling))); return nullptr; } + get_token(); // consume arrow expression_ptr rhs = parse_stoich_expression(); + if (!rhs) return nullptr; if(token_.type != tok::lparen) { error(pprintf("expected '%', found '%'", yellow("("), yellow(token_.spelling))); return nullptr; } + get_token(); // consume lparen expression_ptr fwd = parse_expression(); + if (!fwd) return nullptr; if(token_.type != tok::comma) { error(pprintf("expected '%', found '%'", yellow(","), yellow(token_.spelling))); return nullptr; } + get_token(); // consume comma expression_ptr rev = parse_expression(); + if (!rev) return nullptr; if(token_.type != tok::rparen) { error(pprintf("expected '%', found '%'", yellow(")"), yellow(token_.spelling))); return nullptr; } + get_token(); // consume rparen return make_expression<ReactionExpression>(here, std::move(lhs), std::move(rhs), std::move(fwd), std::move(rev)); } diff --git a/tests/modcc/driver.cpp b/tests/modcc/driver.cpp index f32ac59d1471f94c6fb3d7d6333eeaf86d2e1c28..67505a0d20d3406d1ed8142715fb2243e74c6b0b 100644 --- a/tests/modcc/driver.cpp +++ b/tests/modcc/driver.cpp @@ -2,10 +2,17 @@ * unit test driver **************************************************************/ +#include <cstring> + #include "test.hpp" +bool g_verbose_flag = false; + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); + if (argc>1 && (!std::strcmp(argv[1],"-v") || !std::strcmp(argv[1],"--verbose"))) { + g_verbose_flag = true; + } return RUN_ALL_TESTS(); } diff --git a/tests/modcc/test.hpp b/tests/modcc/test.hpp index 588b75a36afcb09107adc65b862fd65eceb5a9c6..d3de72506d2ef679227fcc71f0d76aab141b3a9c 100644 --- a/tests/modcc/test.hpp +++ b/tests/modcc/test.hpp @@ -5,12 +5,9 @@ #include "parser.hpp" #include "modccutil.hpp" -//#define VERBOSE_TEST -#ifdef VERBOSE_TEST -#define VERBOSE_PRINT(x) std::cout << (x) << std::endl; -#else -#define VERBOSE_PRINT(x) -#endif +extern bool g_verbose_flag; + +#define VERBOSE_PRINT(x) (g_verbose_flag && std::cout << (x) << "\n") inline expression_ptr parse_line_expression(std::string const& s) { return Parser(s).parse_line_expression(); diff --git a/tests/modcc/test_lexer.cpp b/tests/modcc/test_lexer.cpp index b881f5a0a69c344a8ae0a6e9f8ebd2adc46ea495..b8ba84d92c85ac3bb8efc5533c1115efb031d12d 100644 --- a/tests/modcc/test_lexer.cpp +++ b/tests/modcc/test_lexer.cpp @@ -1,11 +1,37 @@ #include <cmath> #include <iterator> +#include <utility> #include "test.hpp" #include "lexer.hpp" -//#define PRINT_LEX_STRING std::cout << "________________\n" << string << "\n________________\n"; -#define PRINT_LEX_STRING +void verbose_print(const char* string) { + if (!g_verbose_flag) return; + std::cout << "________________\n" << string << "\n________________\n"; +} + +void verbose_print(const Token& token) { + if (!g_verbose_flag) return; + std::cout << "tok: " << token << "\n"; +} + +class VerboseLexer: public Lexer { +public: + template <typename... Args> + VerboseLexer(Args&&... args): Lexer(std::forward<Args>(args)...) { + if (g_verbose_flag) { + std::cout << "________________\n" << std::string(begin_, end_) << "\n________________\n"; + } + } + + Token parse() { + auto tok = Lexer::parse(); + if (g_verbose_flag) { + std::cout << "token: " << tok << "\n"; + } + return tok; + } +}; /************************************************************** * lexer tests @@ -13,8 +39,7 @@ // test identifiers TEST(Lexer, identifiers) { char string[] = "_foo:\nbar, buzz f_zz"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + VerboseLexer lexer(string, string+sizeof(string)); auto t1 = lexer.parse(); EXPECT_EQ(t1.type, tok::identifier); @@ -43,9 +68,8 @@ TEST(Lexer, identifiers) { // test keywords TEST(Lexer, keywords) { - char string[] = "NEURON UNITS SOLVE else TITLE CONDUCTANCE"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + char string[] = "NEURON UNITS SOLVE else TITLE CONDUCTANCE KINETIC"; + VerboseLexer lexer(string, string+sizeof(string)); // should skip all white space and go straight to eof auto t1 = lexer.parse(); @@ -69,19 +93,22 @@ TEST(Lexer, keywords) { EXPECT_NE(t5.type, tok::identifier); EXPECT_EQ(t5.spelling, "TITLE"); + auto t6 = lexer.parse(); + EXPECT_EQ(t6.type, tok::conductance); + EXPECT_EQ(t6.spelling, "CONDUCTANCE"); + auto t7 = lexer.parse(); - EXPECT_EQ(t7.type, tok::conductance); - EXPECT_EQ(t7.spelling, "CONDUCTANCE"); + EXPECT_EQ(t7.type, tok::kinetic); + EXPECT_EQ(t7.spelling, "KINETIC"); - auto t6 = lexer.parse(); - EXPECT_EQ(t6.type, tok::eof); + auto tlast = lexer.parse(); + EXPECT_EQ(tlast.type, tok::eof); } // test white space TEST(Lexer, whitespace) { char string[] = " \t\v\f"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + VerboseLexer lexer(string, string+sizeof(string)); // should skip all white space and go straight to eof auto t1 = lexer.parse(); @@ -91,8 +118,7 @@ TEST(Lexer, whitespace) { // test new line TEST(Lexer, newline) { char string[] = "foo \n bar \n +\r\n-"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + VerboseLexer lexer(string, string+sizeof(string)); // get foo auto t1 = lexer.parse(); @@ -123,9 +149,8 @@ TEST(Lexer, newline) { // test operators TEST(Lexer, symbols) { - char string[] = "+-/*, t= ^ h'"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + char string[] = "+-/*, t= ^ h'<->~"; + VerboseLexer lexer(string, string+sizeof(string)); auto t1 = lexer.parse(); EXPECT_EQ(t1.type, tok::plus); @@ -161,13 +186,18 @@ TEST(Lexer, symbols) { EXPECT_EQ(t10.type, tok::prime); auto t11 = lexer.parse(); - EXPECT_EQ(t11.type, tok::eof); + EXPECT_EQ(t11.type, tok::arrow); + + auto t12 = lexer.parse(); + EXPECT_EQ(t12.type, tok::tilde); + + auto tlast = lexer.parse(); + EXPECT_EQ(tlast.type, tok::eof); } TEST(Lexer, comparison_operators) { char string[] = "< <= > >= == != !"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + VerboseLexer lexer(string, string+sizeof(string)); auto t1 = lexer.parse(); EXPECT_EQ(t1.type, tok::lt); @@ -191,8 +221,7 @@ TEST(Lexer, comparison_operators) { // test braces TEST(Lexer, braces) { char string[] = "foo}"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + VerboseLexer lexer(string, string+sizeof(string)); auto t1 = lexer.parse(); EXPECT_EQ(t1.type, tok::identifier); @@ -209,8 +238,7 @@ TEST(Lexer, comments) { char string[] = "foo:this is one line\n" "bar : another comment\n" "foobar ? another comment\n"; - PRINT_LEX_STRING - Lexer lexer(string, string+sizeof(string)); + VerboseLexer lexer(string, string+sizeof(string)); auto t1 = lexer.parse(); EXPECT_EQ(t1.type, tok::identifier); @@ -243,7 +271,7 @@ TEST(Lexer, numbers) { std::vector<long long> check_ints = {1, 23, 3}; std::vector<long long> ints; - Lexer lexer(floats_stream.str()); + VerboseLexer lexer(floats_stream.str()); auto t = lexer.parse(); auto iter = floats.cbegin(); while (t.type != tok::eof && iter != floats.cend()) { diff --git a/tests/modcc/test_optimization.cpp b/tests/modcc/test_optimization.cpp index 9b69cee7d2aa5c16af82e2a143cc9632c9b359b5..01f0a69c7e9cc8ec918a307bb77dd2a00d5e961b 100644 --- a/tests/modcc/test_optimization.cpp +++ b/tests/modcc/test_optimization.cpp @@ -9,23 +9,23 @@ TEST(Optimizer, constant_folding) { auto v = make_unique<ConstantFolderVisitor>(); { auto e = parse_line_expression("x = 2*3"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); EXPECT_EQ(e->is_assignment()->rhs()->is_number()->value(), 6); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT( "" ) + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT( "" ); } { auto e = parse_line_expression("x = 1 + 2 + 3"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); EXPECT_EQ(e->is_assignment()->rhs()->is_number()->value(), 6); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT( "" ) + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT( "" ); } { auto e = parse_line_expression("x = exp(2)"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); // The tolerance has to be loosend to 1e-15, because the optimizer performs // all intermediate calculations in 80 bit precision, which disagrees in @@ -33,24 +33,24 @@ TEST(Optimizer, constant_folding) { // This is a good thing: by using the constant folder we increase accuracy // over the unoptimized code! EXPECT_EQ(std::fabs(e->is_assignment()->rhs()->is_number()->value()-std::exp(2.0))<1e-15, true); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT( "" ) + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT( "" ); } { auto e = parse_line_expression("x= 2*2 + 3"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); EXPECT_EQ(e->is_assignment()->rhs()->is_number()->value(), 7); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT( "" ) + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT( "" ); } { auto e = parse_line_expression("x= 3 + 2*2"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); EXPECT_EQ(e->is_assignment()->rhs()->is_number()->value(), 7); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT( "" ) + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT( "" ); } { // this doesn't work: the (y+2) expression is not a constant, so folding stops. @@ -58,23 +58,23 @@ TEST(Optimizer, constant_folding) { // one approach would be try sorting communtative operations so that numbers // are adjacent to one another in the tree auto e = parse_line_expression("x= y + 2 + 3"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT( "" ) + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT( "" ); } { auto e = parse_line_expression("x= 2 + 3 + y"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT(""); + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT("");; } { auto e = parse_line_expression("foo(2+3, log(32), 2*3 + x)"); - VERBOSE_PRINT( e->to_string() ) + VERBOSE_PRINT( e->to_string() ); e->accept(v.get()); - VERBOSE_PRINT( e->to_string() ) - VERBOSE_PRINT(""); + VERBOSE_PRINT( e->to_string() ); + VERBOSE_PRINT("");; } } diff --git a/tests/modcc/test_parser.cpp b/tests/modcc/test_parser.cpp index 1edfe1a9ad61a0e3837dbddba2877037fb30aee2..dd9a3ea90ee04a26c2893a2aedb6b5fc0a5fa6cc 100644 --- a/tests/modcc/test_parser.cpp +++ b/tests/modcc/test_parser.cpp @@ -1,12 +1,76 @@ #include <cmath> +#include <memory> #include "test.hpp" #include "module.hpp" +#include "modccutil.hpp" #include "parser.hpp" +template <typename EPtr> +void verbose_print(const EPtr& e, Parser& p, const char* text) { + if (!g_verbose_flag) return; + + if (e) std::cout << e->to_string() << "\n"; + if (p.status()==lexerStatus::error) + std::cout << "in " << red(text) << "\t" << p.error_message() << "\n"; +} + +template <typename Derived, typename RetUniqPtr> +::testing::AssertionResult check_parse( + std::unique_ptr<Derived>& derived, + RetUniqPtr (Parser::*pmemfn)(), + const char* text) +{ + Parser p(text); + auto e = (p.*pmemfn)(); + verbose_print(e, p, text); + + if (e==nullptr) { + return ::testing::AssertionFailure() << "failed to parse '" << text << "'"; + } + + if (p.status()!=lexerStatus::happy) { + return ::testing::AssertionFailure() << "parser status is not happy"; + } + + Derived *ptr = e? dynamic_cast<Derived*>(e.get()): nullptr; + if (ptr==nullptr) { + return ::testing::AssertionFailure() << "failed to cast to derived type"; + } + else { + e.release(); + derived.reset(ptr); + } + + return ::testing::AssertionSuccess(); +} + +template <typename RetUniqPtr> +::testing::AssertionResult check_parse(RetUniqPtr (Parser::*pmemfn)(), const char* text) { + std::unique_ptr<Expression> e; + return check_parse(e, pmemfn, text); +} + +template <typename RetUniqPtr> +::testing::AssertionResult check_parse_fail(RetUniqPtr (Parser::*pmemfn)(), const char* text) { + Parser p(text); + auto e = (p.*pmemfn)(); + verbose_print(e, p, text); + + if (p.status()!=lexerStatus::error) { + return ::testing::AssertionFailure() << "parser status is not error"; + } + + if (e!=nullptr) { + return ::testing::AssertionFailure() << "parser returned non-null expression"; + } + + return ::testing::AssertionSuccess(); +} + TEST(Parser, full_file) { Module m(DATADIR "/test.mod"); - if(m.buffer().size()==0) { + if (m.buffer().size()==0) { std::cout << "skipping Parser.full_file test because unable to open input file" << std::endl; return; } @@ -15,481 +79,339 @@ TEST(Parser, full_file) { } TEST(Parser, procedure) { - std::vector<const char*> calls = -{ -"PROCEDURE foo(x, y) {" -" LOCAL a\n" -" LOCAL b\n" -" LOCAL c\n" -" a = 3\n" -" b = x * y + 2\n" -" y = x + y * 2\n" -" y = a + b +c + a + b\n" -" y = a + b *c + a + b\n" -"}" -, -"PROCEDURE trates(v) {\n" -" LOCAL qt\n" -" qt=q10^((celsius-22)/10)\n" -" minf=1-1/(1+exp((v-vhalfm)/km))\n" -" hinf=1/(1+exp((v-vhalfh)/kh))\n" -" mtau = 0.6\n" -" htau = 1500\n" -"}" -}; - for(auto const& str : calls) { - Parser p(str); - auto e = p.parse_procedure(); -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - if(p.status()==lexerStatus::error) { - std::cout << str << std::endl; - std::cout << red("error ") << p.error_message() << std::endl; - } + std::vector<const char*> calls = { + "PROCEDURE foo(x, y) {\n" + " LOCAL a\n" + " LOCAL b\n" + " LOCAL c\n" + " a = 3\n" + " b = x * y + 2\n" + " y = x + y * 2\n" + " y = a + b +c + a + b\n" + " y = a + b *c + a + b\n" + "}" + , + "PROCEDURE trates(v) {\n" + " LOCAL qt\n" + " qt=q10^((celsius-22)/10)\n" + " minf=1-1/(1+exp((v-vhalfm)/km))\n" + " hinf=1/(1+exp((v-vhalfh)/kh))\n" + " mtau = 0.6\n" + " htau = 1500\n" + "}" + }; + + for (const auto& str: calls) { + EXPECT_TRUE(check_parse(&Parser::parse_procedure, str)); } } TEST(Parser, net_receive) { char str[] = - "NET_RECEIVE (x, y) { \n" - " LOCAL a \n" - " a = 3 \n" - " x = a+3 \n" - " y = x+a \n" - "}"; - Parser p(str); - auto e = p.parse_procedure(); - #ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - #endif - - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - auto nr = e->is_symbol()->is_net_receive(); - EXPECT_NE(nr, nullptr); - if(nr) { - EXPECT_EQ(nr->args().size(), (unsigned)2); - } - if(p.status()==lexerStatus::error) { - std::cout << str << std::endl; - std::cout << red("error ") << p.error_message() << std::endl; + "NET_RECEIVE (x, y) { \n" + " LOCAL a \n" + " a = 3 \n" + " x = a+3 \n" + " y = x+a \n" + "}"; + + std::unique_ptr<Symbol> sym; + + EXPECT_TRUE(check_parse(sym, &Parser::parse_procedure, str)); + if (sym) { + auto nr = sym->is_net_receive(); + EXPECT_NE(nullptr, nr); + if (nr) { + EXPECT_EQ(2u, nr->args().size()); + } } } TEST(Parser, function) { - std::vector< const char*> calls = -{ -"FUNCTION foo(x, y) {" -" LOCAL a\n" -" a = 3\n" -" b = x * y + 2\n" -" y = x + y * 2\n" -" foo = a * x + y\n" -"}" -}; - for(auto const& str : calls) { - Parser p(str); - auto e = p.parse_function(); -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - if(p.status()==lexerStatus::error) { - std::cout << str << std::endl; - std::cout << red("error ") << p.error_message() << std::endl; - } - } + char str[] = + "FUNCTION foo(x, y) {" + " LOCAL a\n" + " a = 3\n" + " b = x * y + 2\n" + " y = x + y * 2\n" + " foo = a * x + y\n" + "}"; + + std::unique_ptr<Symbol> sym; + EXPECT_TRUE(check_parse(sym, &Parser::parse_function, str)); } TEST(Parser, parse_solve) { - { - Parser p("SOLVE states METHOD cnexp"); - auto e = p.parse_solve(); - -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - if(e) { - SolveExpression* s = dynamic_cast<SolveExpression*>(e.get()); - EXPECT_EQ(s->method(), solverMethod::cnexp); - EXPECT_EQ(s->name(), "states"); - } + std::unique_ptr<SolveExpression> s; - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) { - std::cout << red("error") << p.error_message() << std::endl; - } + EXPECT_TRUE(check_parse(s, &Parser::parse_solve, "SOLVE states METHOD cnexp")); + if (s) { + EXPECT_EQ(s->method(), solverMethod::cnexp); + EXPECT_EQ(s->name(), "states"); } - { - Parser p("SOLVE states"); - auto e = p.parse_solve(); -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - if(e) { - SolveExpression* s = dynamic_cast<SolveExpression*>(e.get()); - EXPECT_EQ(s->method(), solverMethod::none); - EXPECT_EQ(s->name(), "states"); - } - - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) { - std::cout << red("error") << p.error_message() << std::endl; - } + EXPECT_TRUE(check_parse(s, &Parser::parse_solve, "SOLVE states")); + if (s) { + EXPECT_EQ(s->method(), solverMethod::none); + EXPECT_EQ(s->name(), "states"); } } TEST(Parser, parse_conductance) { - { - Parser p("CONDUCTANCE g USEION na"); - auto e = p.parse_conductance(); - -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - if(e) { - ConductanceExpression* s = dynamic_cast<ConductanceExpression*>(e.get()); - EXPECT_EQ(s->ion_channel(), ionKind::Na); - EXPECT_EQ(s->name(), "g"); - } + std::unique_ptr<ConductanceExpression> s; - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) { - std::cout << red("error") << p.error_message() << std::endl; - } + EXPECT_TRUE(check_parse(s, &Parser::parse_conductance, "CONDUCTANCE g USEION na")); + if (s) { + EXPECT_EQ(s->ion_channel(), ionKind::Na); + EXPECT_EQ(s->name(), "g"); } - { - Parser p("CONDUCTANCE gnda"); - auto e = p.parse_conductance(); -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - if(e) { - ConductanceExpression* s = dynamic_cast<ConductanceExpression*>(e.get()); - EXPECT_EQ(s->ion_channel(), ionKind::nonspecific); - EXPECT_EQ(s->name(), "gnda"); - } - - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) { - std::cout << red("error") << p.error_message() << std::endl; - } + EXPECT_TRUE(check_parse(s, &Parser::parse_conductance, "CONDUCTANCE gnda")); + if (s) { + EXPECT_EQ(s->ion_channel(), ionKind::nonspecific); + EXPECT_EQ(s->name(), "gnda"); } } TEST(Parser, parse_if) { - { - char expression[] = + std::unique_ptr<IfExpression> s; + + EXPECT_TRUE(check_parse(s, &Parser::parse_if, " if(a<b) { \n" " a = 2+b \n" " b = 4^b \n" - " } \n"; - Parser p(expression); - auto e = p.parse_if(); - EXPECT_NE(e, nullptr); - if(e) { - auto ife = e->is_if(); - EXPECT_NE(e->is_if(), nullptr); - if(ife) { - EXPECT_NE(ife->condition()->is_binary(), nullptr); - EXPECT_NE(ife->true_branch()->is_block(), nullptr); - EXPECT_EQ(ife->false_branch(), nullptr); - } - //std::cout << e->to_string() << std::endl; - } - else { - std::cout << p.error_message() << std::endl; - } + " } \n" + )); + if (s) { + EXPECT_NE(s->condition()->is_binary(), nullptr); + EXPECT_NE(s->true_branch()->is_block(), nullptr); + EXPECT_EQ(s->false_branch(), nullptr); } - { - char expression[] = + + EXPECT_TRUE(check_parse(s, &Parser::parse_if, " if(a<b) { \n" " a = 2+b \n" " } else { \n" " a = 2+b \n" - " } "; - Parser p(expression); - auto e = p.parse_if(); - EXPECT_NE(e, nullptr); - if(e) { - auto ife = e->is_if(); - EXPECT_NE(ife, nullptr); - if(ife) { - EXPECT_NE(ife->condition()->is_binary(), nullptr); - EXPECT_NE(ife->true_branch()->is_block(), nullptr); - EXPECT_NE(ife->false_branch(), nullptr); - } - //std::cout << std::endl << e->to_string() << std::endl; - } - else { - std::cout << p.error_message() << std::endl; - } + " } " + )); + if (s) { + EXPECT_NE(s->condition()->is_binary(), nullptr); + EXPECT_NE(s->true_branch()->is_block(), nullptr); + EXPECT_NE(s->false_branch(), nullptr); } - { - char expression[] = + + EXPECT_TRUE(check_parse(s, &Parser::parse_if, " if(a<b) { \n" " a = 2+b \n" " } else if(b>a){\n" " a = 2+b \n" - " } "; - Parser p(expression); - auto e = p.parse_if(); - EXPECT_NE(e, nullptr); - if(e) { - auto ife = e->is_if(); - EXPECT_NE(ife, nullptr); - if(ife) { - EXPECT_NE(ife->condition()->is_binary(), nullptr); - EXPECT_NE(ife->true_branch()->is_block(), nullptr); - EXPECT_NE(ife->false_branch(), nullptr); - EXPECT_NE(ife->false_branch()->is_if(), nullptr); - EXPECT_EQ(ife->false_branch()->is_if()->false_branch(), nullptr); - } - //std::cout << std::endl << e->to_string() << std::endl; - } - else { - std::cout << p.error_message() << std::endl; - } + " } " + )); + if (s) { + EXPECT_NE(s->condition()->is_binary(), nullptr); + EXPECT_NE(s->true_branch()->is_block(), nullptr); + ASSERT_NE(s->false_branch(), nullptr); + ASSERT_NE(s->false_branch()->is_if(), nullptr); + EXPECT_EQ(s->false_branch()->is_if()->false_branch(), nullptr); } } TEST(Parser, parse_local) { - ////////////////////// test for valid expressions ////////////////////// - { - Parser p("LOCAL xyz"); - auto e = p.parse_local(); - - #ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - #endif - EXPECT_NE(e, nullptr); - if(e) { - EXPECT_NE(e->is_local_declaration(), nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - } + std::unique_ptr<LocalDeclaration> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_local, "LOCAL xyz")); + if (s) { + ASSERT_EQ(1u, s->variables().size()); + } - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) - std::cout << red("error") << p.error_message() << std::endl; - } - - { - Parser p("LOCAL x, y, z"); - auto e = p.parse_local(); - - #ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - #endif - EXPECT_NE(e, nullptr); - if(e) { - EXPECT_NE(e->is_local_declaration(), nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - auto vars = e->is_local_declaration()->variables(); - EXPECT_EQ(vars.size(), (unsigned)3); - EXPECT_NE(vars.find("x"), vars.end()); - EXPECT_NE(vars.find("y"), vars.end()); - EXPECT_NE(vars.find("z"), vars.end()); - } + EXPECT_TRUE(check_parse(s, &Parser::parse_local, "LOCAL x, y, z")); + if (s) { + auto vars = s->variables(); + ASSERT_EQ(3u, vars.size()); + ASSERT_TRUE(vars.count("x")); + ASSERT_TRUE(vars.count("y")); + ASSERT_TRUE(vars.count("z")); + } + + EXPECT_TRUE(check_parse_fail(&Parser::parse_local, "LOCAL x,")); +} - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) - std::cout << red("error") << p.error_message() << std::endl; +TEST(Parser, parse_unary_expression) { + const char* good_expr[] = { + "+x ", + "-x ", + "(x + -y) ", + "-(x - + -y) ", + "exp(x + y) ", + "-exp(x + -y) " + }; + + for (auto& text: good_expr) { + EXPECT_TRUE(check_parse(&Parser::parse_unaryop, text)); } +} + +// test parsing of parenthesis expressions +TEST(Parser, parse_parenthesis_expression) { + const char* good_expr[] = { + "((celsius-22)/10) ", + "((celsius-22)+10) ", + "(x+2) ", + "((x)) ", + "(((x))) ", + "(x + (x * (y*(2)) + 4))", + }; - ////////////////////// test for invalid expressions ////////////////////// - { - Parser p("LOCAL 2"); - auto e = p.parse_local(); + for (auto& text: good_expr) { + EXPECT_TRUE(check_parse(&Parser::parse_parenthesis_expression, text)); + } - EXPECT_EQ(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::error); + const char* bad_expr[] = { + "(x ", + "((x+3) ", + "(x+ +) ", + "(x=3) ", // assignment inside parenthesis isn't allowed + "(a + (b*2^(x)) ", // missing closing parenthesis + }; - #ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - if(p.status()==lexerStatus::error) - std::cout << "in " << cyan(bad_expression) << "\t" << p.error_message() << std::endl; - #endif + for (auto& text: bad_expr) { + EXPECT_TRUE(check_parse_fail(&Parser::parse_parenthesis_expression, text)); } +} + +// test parsing of line expressions +TEST(Parser, parse_line_expression) { + const char* good_expr[] = { + "qt=q10^((celsius-22)/10)" + "x=2 ", + "x=2 ", + "x = -y\n " + "x=2*y ", + "x=y + 2 * z", + "x=(y + 2) * z ", + "x=(y + 2) * z ^ 3 ", + "x=(y + 2 * z ^ 3) ", + "foo(x+3, y, bar(21.4))", + "y=exp(x+3) + log(exp(x/y))", + "a=x^y^z", + "a=x/y/z" + }; - { - Parser p("LOCAL x, "); - auto e = p.parse_local(); + for (auto& text: good_expr) { + EXPECT_TRUE(check_parse(&Parser::parse_line_expression, text)); + } - EXPECT_EQ(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::error); + const char* bad_expr[] = { + "x=2+ ", // incomplete binary expression on rhs + "x= ", // missing rhs of assignment + "x=)y + 2 * z", + "x=(y + 2 ", + "x=(y ++ z ", + "x/=3 ", // compound binary expressions not supported + "foo+8 ", // missing assignment + "foo()=8 ", // lhs of assingment must be an lvalue + }; - #ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - if(p.status()==lexerStatus::error) - std::cout << "in " << cyan(bad_expression) << "\t" << p.error_message() << std::endl; - #endif + for (auto& text: bad_expr) { + EXPECT_TRUE(check_parse_fail(&Parser::parse_line_expression, text)); } } -TEST(Parser, parse_unary_expression) { - std::vector<const char*> good_expressions = - { -"+x ", -"-x ", -"(x + -y) ", -"-(x - + -y) ", -"exp(x + y) ", -"-exp(x + -y) ", +TEST(Parser, parse_stoich_term) { + const char* good_expr[] = { + "B", "B3", "3B3", "0A", "12A" }; - for(auto const& expression : good_expressions) { - Parser p(expression); - auto e = p.parse_unaryop(); + for (auto& text: good_expr) { + std::unique_ptr<StoichTermExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_term, text)); + } -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); + const char* bad_expr[] = { + "-A", "-3A", "0.2A", "5" + }; - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) - std::cout << red("error") << p.error_message() << std::endl; + for (auto& text: bad_expr) { + EXPECT_TRUE(check_parse_fail(&Parser::parse_stoich_term, text)); } } -// test parsing of parenthesis expressions -TEST(Parser, parse_parenthesis_expression) { - std::vector<const char*> good_expressions = - { -"((celsius-22)/10) ", -"((celsius-22)+10) ", -"(x+2) ", -"((x)) ", -"(((x))) ", -"(x + (x * (y*(2)) + 4))", +TEST(Parser, parse_stoich_expression) { + const char* single_expr[] = { + "B", "B3", "3xy" }; - for(auto const& expression : good_expressions) { - Parser p(expression); - auto e = p.parse_parenthesis_expression(); - -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) - std::cout << cyan(expression) << "\t" - << red("error") << p.error_message() << std::endl; - } - - std::vector<const char*> bad_expressions = - { -"(x ", -"((x+3) ", -"(x+ +) ", -"(x=3) ", // assignment inside parenthesis isn't allowed -"(a + (b*2^(x)) ", // missing closing parenthesis + for (auto& text: single_expr) { + std::unique_ptr<StoichExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_expression, text)); + EXPECT_EQ(1, s->terms().size()); + } + + const char* double_expr[] = { + "B+A", "a1 + 2bn", "4c+d" }; - for(auto const& expression : bad_expressions) { - Parser p(expression); - auto e = p.parse_parenthesis_expression(); + for (auto& text: double_expr) { + std::unique_ptr<StoichExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_expression, text)); + EXPECT_EQ(2, s->terms().size()); + } - EXPECT_EQ(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::error); + const char* other_good_expr[] = { + "", "a+b+c", "1a+2b+3c+4d" + }; -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - if(p.status()==lexerStatus::error) - std::cout << "in " << cyan(expression) << "\t" << p.error_message() << std::endl; -#endif + for (auto& text: other_good_expr) { + std::unique_ptr<StoichExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_expression, text)); } -} -// test parsing of line expressions -TEST(Parser, parse_line_expression) { - std::vector<const char*> good_expressions = - { -"qt=q10^((celsius-22)/10)" -"x=2 ", -"x=2 ", -"x = -y\n " -"x=2*y ", -"x=y + 2 * z", -"x=(y + 2) * z ", -"x=(y + 2) * z ^ 3 ", -"x=(y + 2 * z ^ 3) ", -"foo(x+3, y, bar(21.4))", -"y=exp(x+3) + log(exp(x/y))", -"a=x^y^z", -"a=x/y/z" + const char* bad_expr[] = { + "A+B+", "A+5+B" }; - for(auto const& expression : good_expressions) { - Parser p(expression); - auto e = p.parse_line_expression(); - -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); - - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) - std::cout << red("error") << p.error_message() << std::endl; - } - - std::vector<const char*> bad_expressions = - { -"x=2+ ", // incomplete binary expression on rhs -"x= ", // missing rhs of assignment -"x=)y + 2 * z", -"x=(y + 2 ", -"x=(y ++ z ", -"x/=3 ", // compound binary expressions not supported -"foo+8 ", // missing assignment -"foo()=8 ", // lhs of assingment must be an lvalue + for (auto& text: bad_expr) { + EXPECT_TRUE(check_parse_fail(&Parser::parse_stoich_expression, text)); + } +} + +// test parsing of stoich and reaction expressions +TEST(Parser, parse_reaction_expression) { + const char* good_expr[] = { + "~ A + B <-> C + D (k1, k2)", + "~ 2B <-> C + D + E (k1(3,v), k2)", + "~ <-> C + D + 7 E (k1, f(a,b)-2)", + "~ <-> (f,g)", + "~ A + 3B + C<-> (f,g)" }; - for(auto const& expression : bad_expressions) { - Parser p(expression); - auto e = p.parse_line_expression(); + for (auto& text: good_expr) { + std::unique_ptr<ReactionExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_reaction_expression, text)); + } - EXPECT_EQ(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::error); + const char* bad_expr[] = { + "~ A + B <-> C + D (k1, k2, k3)", + "~ A + B <-> C + (k1, k2)", + "~ 2.3B <-> C + D + E (k1(3,v), k2)", + "~ <-> C + D + 7E", + "~ <-> (,g)", + "~ A - 3B + C<-> (f,g)", + " A <-> B (k1, k2)", + "~ A <- B (k1)", + "~ A -> B (k2)", + }; -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; - if(p.status()==lexerStatus::error) - std::cout << "in " << cyan(expression) << "\t" << p.error_message() << std::endl; -#endif + for (auto& text: bad_expr) { + EXPECT_TRUE(check_parse_fail(&Parser::parse_reaction_expression, text)); } } long double eval(Expression *e) { - if(auto n = e->is_number()) { + if (auto n = e->is_number()) { return n->value(); } - if(auto b = e->is_binary()) { + if (auto b = e->is_binary()) { auto lhs = eval(b->lhs()); auto rhs = eval(b->rhs()); switch(b->op()) { @@ -501,7 +423,7 @@ long double eval(Expression *e) { default:; } } - if(auto u = e->is_unary()) { + if (auto u = e->is_unary()) { auto val = eval(u->expression()); switch(u->op()) { case tok::plus : return val; @@ -515,8 +437,7 @@ long double eval(Expression *e) { // test parsing of expressions for correctness // by parsing rvalue expressions with numeric atoms, which can be evalutated using eval TEST(Parser, parse_binop) { - std::vector<std::pair<const char*, double>> tests = - { + std::pair<const char*, double> tests[] = { // simple {"2+3", 2.+3.}, {"2-3", 2.-3.}, @@ -540,15 +461,9 @@ TEST(Parser, parse_binop) { {"3^2*5.", std::pow(3.,2.)*5.}, }; - for(auto const& test_case : tests) { - Parser p(test_case.first); - auto e = p.parse_expression(); - -#ifdef VERBOSE_TEST - if(e) std::cout << e->to_string() << std::endl; -#endif - EXPECT_NE(e, nullptr); - EXPECT_EQ(p.status(), lexerStatus::happy); + for (const auto& test_case: tests) { + std::unique_ptr<Expression> e; + EXPECT_TRUE(check_parse(e, &Parser::parse_expression, test_case.first)); // A loose tolerance of 1d-10 is required here because the eval() // function uses long double for intermediate results (like constant @@ -556,15 +471,11 @@ TEST(Parser, parse_binop) { // operations this can see relatively large divergence between the // double and long double results. EXPECT_NEAR(eval(e.get()), test_case.second, 1e-10); - - // always print the compiler errors, because they are unexpected - if(p.status()==lexerStatus::error) - std::cout << red("error") << p.error_message() << std::endl; } } TEST(Parser, parse_state_block) { - std::vector<const char*> state_blocks = { + const char* state_blocks[] = { "STATE {\n" " h\n" " m r\n" @@ -587,14 +498,12 @@ TEST(Parser, parse_state_block) { "}" }; - for (auto const& str: state_blocks) { - Module m(str, sizeof(str)); + expression_ptr null; + for (auto& text: state_blocks) { + Module m(text, sizeof(text)); Parser p(m, false); p.parse_state_block(); EXPECT_EQ(lexerStatus::happy, p.status()); - if (p.status() == lexerStatus::error) { - std::cout << str << "\n" - << red("error") << p.error_message() << "\n"; - } + verbose_print(null, p, text); } }