diff --git a/modcc/expression.cpp b/modcc/expression.cpp index 05fdb33dd23c5516384057e5cc2946b5c8c6fab4..e2694ea6f36e5dc33622e9d4fbe725fbd13d24d4 100644 --- a/modcc/expression.cpp +++ b/modcc/expression.cpp @@ -274,8 +274,12 @@ void ReactionExpression::semantic(std::shared_ptr<scope_type> scp) { scope_ = scp; lhs()->semantic(scp); rhs()->semantic(scp); + fwd_rate()->semantic(scp); rev_rate()->semantic(scp); + if(fwd_rate_->is_procedure_call() || rev_rate_->is_procedure_call()) { + error("procedure calls can't be made in an expression"); + } } /******************************************************************************* @@ -323,6 +327,25 @@ void StoichExpression::semantic(std::shared_ptr<scope_type> scp) { } } +/******************************************************************************* + ConserveExpression +*******************************************************************************/ + +expression_ptr ConserveExpression::clone() const { + return make_expression<ConserveExpression>( + location_, lhs()->clone(), rhs()->clone()); +} + +void ConserveExpression::semantic(std::shared_ptr<scope_type> scp) { + scope_ = scp; + lhs_->semantic(scp); + rhs_->semantic(scp); + + if(rhs_->is_procedure_call()) { + error("procedure calls can't be made in an expression"); + } +} + /******************************************************************************* CallExpression *******************************************************************************/ @@ -869,6 +892,9 @@ void BinaryExpression::accept(Visitor *v) { void AssignmentExpression::accept(Visitor *v) { v->visit(this); } +void ConserveExpression::accept(Visitor *v) { + v->visit(this); +} void ReactionExpression::accept(Visitor *v) { v->visit(this); } diff --git a/modcc/expression.hpp b/modcc/expression.hpp index a9f752ec382fbac4df6ff83000bdb03532a3eb4b..a7d84e61a6a72220e4bbc34e692de703e1681a94 100644 --- a/modcc/expression.hpp +++ b/modcc/expression.hpp @@ -44,6 +44,7 @@ class AssignmentExpression; class ReactionExpression; class StoichExpression; class StoichTermExpression; +class ConserveExpression; class AddBinaryExpression; class SubBinaryExpression; class MulBinaryExpression; @@ -166,7 +167,10 @@ public: virtual BinaryExpression* is_binary() {return nullptr;} virtual UnaryExpression* is_unary() {return nullptr;} virtual AssignmentExpression* is_assignment() {return nullptr;} + virtual ConserveExpression* is_conserve() {return nullptr;} virtual ReactionExpression* is_reaction() {return nullptr;} + virtual StoichExpression* is_stoich() {return nullptr;} + virtual StoichTermExpression* is_stoich_term() {return nullptr;} virtual ConditionalExpression* is_conditional() {return nullptr;} virtual InitialBlock* is_initial_block() {return nullptr;} virtual SolveExpression* is_solve_statement() {return nullptr;} @@ -876,6 +880,8 @@ public: coeff_(std::move(coeff)), ident_(std::move(ident)) {} + StoichTermExpression* is_stoich_term() override {return this;} + std::string to_string() const override { return pprintf("% %", coeff()->to_string(), ident()->to_string()); } @@ -889,6 +895,11 @@ public: expression_ptr& ident() { return ident_; } const expression_ptr& ident() const { return ident_; } + bool negative() const { + auto iexpr = coeff_->is_integer(); + return iexpr && iexpr->integer_value()<0; + } + private: expression_ptr coeff_; expression_ptr ident_; @@ -904,6 +915,8 @@ public: : Expression(loc) {} + StoichExpression* is_stoich() override {return this;} + std::string to_string() const override; void semantic(std::shared_ptr<scope_type> scp) override; expression_ptr clone() const override; @@ -1228,6 +1241,20 @@ public: void accept(Visitor *v) override; }; +class ConserveExpression : public BinaryExpression { +public: + ConserveExpression(Location loc, expression_ptr&& lhs, expression_ptr&& rhs) + : BinaryExpression(loc, tok::eq, std::move(lhs), std::move(rhs)) + {} + + ConserveExpression* is_conserve() override {return this;} + expression_ptr clone() const override; + + void semantic(std::shared_ptr<scope_type> scp) override; + + void accept(Visitor *v) override; +}; + class AddBinaryExpression : public BinaryExpression { public: AddBinaryExpression(Location loc, expression_ptr&& lhs, expression_ptr&& rhs) diff --git a/modcc/parser.cpp b/modcc/parser.cpp index 173da807b6287969c18874230725b0d2f99582f1..43855a7ef1ad0915e7312aea048c23448015e06a 100644 --- a/modcc/parser.cpp +++ b/modcc/parser.cpp @@ -817,6 +817,8 @@ expression_ptr Parser::parse_statement() { return parse_local(); case tok::identifier : return parse_line_expression(); + case tok::conserve : + return parse_conserve_expression(); case tok::tilde : return parse_reaction_expression(); case tok::initial : @@ -950,6 +952,12 @@ expression_ptr Parser::parse_line_expression() { expression_ptr Parser::parse_stoich_term() { expression_ptr coeff = make_expression<IntegerExpression>(location_, 1); auto here = location_; + bool negative = false; + + while(token_.type==tok::minus) { + negative = !negative; + get_token(); // consume '-' + } if(token_.type==tok::integer) { coeff = parse_integer(); @@ -959,6 +967,10 @@ expression_ptr Parser::parse_stoich_term() { error(pprintf("expected an identifier, found '%'", yellow(token_.spelling))); return nullptr; } + + if(negative) { + coeff = make_expression<IntegerExpression>(here, -coeff->is_integer()->integer_value()); + } return make_expression<StoichTermExpression>(here, std::move(coeff), parse_identifier()); } @@ -966,14 +978,16 @@ expression_ptr Parser::parse_stoich_expression() { std::vector<expression_ptr> terms; auto here = location_; - if(token_.type==tok::integer || token_.type==tok::identifier) { + if(token_.type==tok::integer || token_.type==tok::identifier || token_.type==tok::minus) { auto term = parse_stoich_term(); if (!term) return nullptr; terms.push_back(std::move(term)); - while(token_.type==tok::plus) { - get_token(); // consume plus + while(token_.type==tok::plus || token_.type==tok::minus) { + if (token_.type==tok::plus) { + get_token(); // consume plus + } auto term = parse_stoich_term(); if (!term) return nullptr; @@ -997,6 +1011,18 @@ expression_ptr Parser::parse_reaction_expression() { expression_ptr lhs = parse_stoich_expression(); if (!lhs) return nullptr; + // reaction halves must comprise non-negative terms + for (const auto& term: lhs->is_stoich()->terms()) { + // should always be true + if (auto sterm = term->is_stoich_term()) { + if (sterm->negative()) { + error(pprintf("expected only non-negative terms in reaction lhs, found '%'", + yellow(term->to_string()))); + return nullptr; + } + } + } + if(token_.type != tok::arrow) { error(pprintf("expected '%', found '%'", yellow("<->"), yellow(token_.spelling))); return nullptr; @@ -1006,6 +1032,17 @@ expression_ptr Parser::parse_reaction_expression() { expression_ptr rhs = parse_stoich_expression(); if (!rhs) return nullptr; + for (const auto& term: rhs->is_stoich()->terms()) { + // should always be true + if (auto sterm = term->is_stoich_term()) { + if (sterm->negative()) { + error(pprintf("expected only non-negative terms in reaction rhs, found '%'", + yellow(term->to_string()))); + return nullptr; + } + } + } + if(token_.type != tok::lparen) { error(pprintf("expected '%', found '%'", yellow("("), yellow(token_.spelling))); return nullptr; @@ -1034,6 +1071,30 @@ expression_ptr Parser::parse_reaction_expression() { std::move(fwd), std::move(rev)); } +expression_ptr Parser::parse_conserve_expression() { + auto here = location_; + + if(token_.type!=tok::conserve) { + error(pprintf("expected '%', found '%'", yellow("CONSERVE"), yellow(token_.spelling))); + return nullptr; + } + + get_token(); // consume 'CONSERVE' + auto lhs = parse_stoich_expression(); + if (!lhs) return nullptr; + + if(token_.type != tok::eq) { + error(pprintf("expected '%', found '%'", yellow("="), yellow(token_.spelling))); + return nullptr; + } + + get_token(); // consume '=' + auto rhs = parse_expression(); + if (!rhs) return nullptr; + + return make_expression<ConserveExpression>(here, std::move(lhs), std::move(rhs)); +} + expression_ptr Parser::parse_expression() { auto lhs = parse_unaryop(); diff --git a/modcc/parser.hpp b/modcc/parser.hpp index ed7b3317b5bea5a6ff26c213e7bbbcea645298c5..dc673e9d7b9f9d1084f8d024801ec4eaf87b9d88 100644 --- a/modcc/parser.hpp +++ b/modcc/parser.hpp @@ -27,6 +27,7 @@ public: expression_ptr parse_stoich_expression(); expression_ptr parse_stoich_term(); expression_ptr parse_reaction_expression(); + expression_ptr parse_conserve_expression(); expression_ptr parse_binop(expression_ptr&&, Token); expression_ptr parse_unaryop(); expression_ptr parse_local(); diff --git a/modcc/token.cpp b/modcc/token.cpp index 58de39c9377c695a450e342a12bbadd205cab120..7383ca9fc684b4f47122a98e8f1af035724c8d9a 100644 --- a/modcc/token.cpp +++ b/modcc/token.cpp @@ -43,6 +43,7 @@ static Keyword keywords[] = { {"WRITE", tok::write}, {"RANGE", tok::range}, {"LOCAL", tok::local}, + {"CONSERVE", tok::conserve}, {"SOLVE", tok::solve}, {"THREADSAFE", tok::threadsafe}, {"GLOBAL", tok::global}, diff --git a/modcc/token.hpp b/modcc/token.hpp index 2f0d23caadeb3bb8a84704618aa47e368c21f3fd..c31cdbc409a2dd411cc5c074bd351fd994d4d029 100644 --- a/modcc/token.hpp +++ b/modcc/token.hpp @@ -57,7 +57,7 @@ enum class tok { unitsoff, unitson, suffix, nonspecific_current, useion, read, write, - range, local, + range, local, conserve, solve, method, threadsafe, global, point_process, diff --git a/tests/modcc/test_lexer.cpp b/tests/modcc/test_lexer.cpp index b8ba84d92c85ac3bb8efc5533c1115efb031d12d..2b49e3b120049bdd542d20a246b5095cbc66fbde 100644 --- a/tests/modcc/test_lexer.cpp +++ b/tests/modcc/test_lexer.cpp @@ -68,7 +68,7 @@ TEST(Lexer, identifiers) { // test keywords TEST(Lexer, keywords) { - char string[] = "NEURON UNITS SOLVE else TITLE CONDUCTANCE KINETIC"; + char string[] = "NEURON UNITS SOLVE else TITLE CONDUCTANCE KINETIC CONSERVE LOCAL"; VerboseLexer lexer(string, string+sizeof(string)); // should skip all white space and go straight to eof @@ -101,6 +101,14 @@ TEST(Lexer, keywords) { EXPECT_EQ(t7.type, tok::kinetic); EXPECT_EQ(t7.spelling, "KINETIC"); + auto t8 = lexer.parse(); + EXPECT_EQ(t8.type, tok::conserve); + EXPECT_EQ(t8.spelling, "CONSERVE"); + + auto t9 = lexer.parse(); + EXPECT_EQ(t9.type, tok::local); + EXPECT_EQ(t9.spelling, "LOCAL"); + auto tlast = lexer.parse(); EXPECT_EQ(tlast.type, tok::eof); } diff --git a/tests/modcc/test_parser.cpp b/tests/modcc/test_parser.cpp index dd9a3ea90ee04a26c2893a2aedb6b5fc0a5fa6cc..2f1bb0d76b182916876eed028160684f217b4062 100644 --- a/tests/modcc/test_parser.cpp +++ b/tests/modcc/test_parser.cpp @@ -318,17 +318,27 @@ TEST(Parser, parse_line_expression) { } TEST(Parser, parse_stoich_term) { - const char* good_expr[] = { + const char* good_pos_expr[] = { "B", "B3", "3B3", "0A", "12A" }; - for (auto& text: good_expr) { + for (auto& text: good_pos_expr) { std::unique_ptr<StoichTermExpression> s; EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_term, text)); + EXPECT_TRUE((s && !s->negative())); } + const char* good_neg_expr[] = { + "-3B3", "-A", "-12A" + }; + + for (auto& text: good_neg_expr) { + std::unique_ptr<StoichTermExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_term, text)); + EXPECT_TRUE((s && s->negative())); + } const char* bad_expr[] = { - "-A", "-3A", "0.2A", "5" + "0.2A", "5" }; for (auto& text: bad_expr) { @@ -358,7 +368,7 @@ TEST(Parser, parse_stoich_expression) { } const char* other_good_expr[] = { - "", "a+b+c", "1a+2b+3c+4d" + "", "a+b+c", "1a-2b+3c+4d" }; for (auto& text: other_good_expr) { @@ -366,6 +376,18 @@ TEST(Parser, parse_stoich_expression) { EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_expression, text)); } + const char* check_coeff = "-3a+2b-c+d"; + { + std::unique_ptr<StoichExpression> s; + EXPECT_TRUE(check_parse(s, &Parser::parse_stoich_expression, check_coeff)); + EXPECT_EQ(4, s->terms().size()); + std::vector<int> confirm = {-3,2,-1,1}; + for (unsigned i = 0; i<4; ++i) { + auto term = s->terms()[i]->is_stoich_term(); + EXPECT_EQ(confirm[i], term->coeff()->is_integer()->integer_value()); + } + } + const char* bad_expr[] = { "A+B+", "A+5+B" }; @@ -407,6 +429,58 @@ TEST(Parser, parse_reaction_expression) { } } +TEST(Parser, parse_conserve) { + std::unique_ptr<ConserveExpression> s; + const char* text; + + text = "CONSERVE a + b = 1"; + ASSERT_TRUE(check_parse(s, &Parser::parse_conserve_expression, text)); + EXPECT_TRUE(s->rhs()->is_number()); + ASSERT_TRUE(s->lhs()->is_stoich()); + EXPECT_EQ(2, s->lhs()->is_stoich()->terms().size()); + + text = "CONSERVE a = 1.23e-2"; + ASSERT_TRUE(check_parse(s, &Parser::parse_conserve_expression, text)); + EXPECT_TRUE(s->rhs()->is_number()); + ASSERT_TRUE(s->lhs()->is_stoich()); + EXPECT_EQ(1, s->lhs()->is_stoich()->terms().size()); + + text = "CONSERVE = 0"; + ASSERT_TRUE(check_parse(s, &Parser::parse_conserve_expression, text)); + EXPECT_TRUE(s->rhs()->is_number()); + ASSERT_TRUE(s->lhs()->is_stoich()); + EXPECT_EQ(0, s->lhs()->is_stoich()->terms().size()); + + text = "CONSERVE -2a + b -c = foo*2.3-bar"; + ASSERT_TRUE(check_parse(s, &Parser::parse_conserve_expression, text)); + EXPECT_TRUE(s->rhs()->is_binary()); + ASSERT_TRUE(s->lhs()->is_stoich()); + { + auto& terms = s->lhs()->is_stoich()->terms(); + ASSERT_EQ(3, terms.size()); + auto coeff = terms[0]->is_stoich_term()->coeff()->is_integer(); + ASSERT_TRUE(coeff); + EXPECT_EQ(-2, coeff->integer_value()); + coeff = terms[1]->is_stoich_term()->coeff()->is_integer(); + ASSERT_TRUE(coeff); + EXPECT_EQ(1, coeff->integer_value()); + coeff = terms[2]->is_stoich_term()->coeff()->is_integer(); + ASSERT_TRUE(coeff); + EXPECT_EQ(-1, coeff->integer_value()); + } + + const char* bad_expr[] = { + "CONSERVE a + 3*b -c = 1", + "CONSERVE a + 3b -c = ", + "a+b+c = 2", + "CONSERVE a + 3b +c" + }; + + for (auto& text: bad_expr) { + EXPECT_TRUE(check_parse_fail(&Parser::parse_conserve_expression, text)); + } +} + long double eval(Expression *e) { if (auto n = e->is_number()) { return n->value(); @@ -507,3 +581,17 @@ TEST(Parser, parse_state_block) { verbose_print(null, p, text); } } + +TEST(Parser, parse_kinetic) { + char str[] = + "KINETIC kin {\n" + " rates(v) \n" + " ~ s1 <-> s2 (f1, r1) \n" + " ~ s2 <-> s3 (f2, r2) \n" + " ~ s2 <-> s4 (f3, r3) \n" + " CONSERVE s1 + s3 + s4 - s2 = 2.3\n" + "}"; + + std::unique_ptr<Symbol> sym; + EXPECT_TRUE(check_parse(sym, &Parser::parse_procedure, str)); +}