From b6aec69654f43ea6757201dc9dcc6a354d3efbea Mon Sep 17 00:00:00 2001
From: Vasileios Karakasis <karakasis@cscs.ch>
Date: Fri, 21 Oct 2016 20:24:08 -0400
Subject: [PATCH] Support for units syntax within state block.

---
 data/test.mod               |  4 ++--
 modcc/blocks.hpp            | 46 ++++++++++++++++++-------------------
 modcc/module.cpp            |  4 ++--
 modcc/parser.cpp            | 32 ++++++++++++++++++++------
 tests/modcc/test_parser.cpp |  9 ++++----
 5 files changed, 57 insertions(+), 38 deletions(-)

diff --git a/data/test.mod b/data/test.mod
index d7403c79..0f95e717 100644
--- a/data/test.mod
+++ b/data/test.mod
@@ -10,8 +10,8 @@ NEURON  {
 }
 
 STATE {
-    h
-    m r
+    h (mA)
+    m (mV) r (S)
 }
 
 UNITS   {
diff --git a/modcc/blocks.hpp b/modcc/blocks.hpp
index b9e41bfa..3ea27608 100644
--- a/modcc/blocks.hpp
+++ b/modcc/blocks.hpp
@@ -26,6 +26,28 @@ enum class moduleKind {
     density
 };
 
+typedef std::vector<Token> unit_tokens;
+struct Id {
+    Token token;
+    std::string value; // store the value as a string, not a number : empty
+                       // string == no value
+    unit_tokens units;
+
+    Id(Token const& t, std::string const& v, unit_tokens const& u)
+        : token(t), value(v), units(u)
+    {}
+
+    Id() {}
+
+    bool has_value() const {
+        return value.size()>0;
+    }
+
+    std::string const& name() const {
+        return token.spelling;
+    }
+};
+
 // information stored in a NEURON {} block in mod file.
 struct NeuronBlock {
     bool threadsafe = false;
@@ -42,7 +64,7 @@ struct NeuronBlock {
 
 // information stored in a NEURON {} block in mod file
 struct StateBlock {
-    std::vector<std::string> state_variables;
+    std::vector<Id> state_variables;
     auto begin() -> decltype(state_variables.begin()) {
         return state_variables.begin();
     }
@@ -52,32 +74,11 @@ struct StateBlock {
 };
 
 // information stored in a NEURON {} block in mod file
-typedef std::vector<Token> unit_tokens;
 struct UnitsBlock {
     typedef std::pair<unit_tokens, unit_tokens> units_pair;
     std::vector<units_pair> unit_aliases;
 };
 
-struct Id {
-    Token token;
-    std::string value; // store the value as a string, not a number : empty string == no value
-    unit_tokens units;
-
-    Id(Token const& t, std::string const& v, unit_tokens const& u)
-        : token(t), value(v), units(u)
-    {}
-
-    Id() {}
-
-    bool has_value() const {
-        return value.size()>0;
-    }
-
-    std::string const& name() const {
-        return token.spelling;
-    }
-};
-
 // information stored in a NEURON {} block in mod file
 struct ParameterBlock {
     std::vector<Id> parameters;
@@ -164,4 +165,3 @@ inline std::ostream& operator<< (std::ostream& os, AssignedBlock const& A) {
 
     return os;
 }
-
diff --git a/modcc/module.cpp b/modcc/module.cpp
index 6001e1b4..c9f749e9 100644
--- a/modcc/module.cpp
+++ b/modcc/module.cpp
@@ -612,7 +612,7 @@ void Module::add_variables_to_symbols() {
 
     // add state variables
     for(auto const &var : state_block()) {
-        VariableExpression *id = new VariableExpression(Location(), var);
+        VariableExpression *id = new VariableExpression(Location(), var.name());
 
         id->state(true);    // set state to true
         // state variables are private
@@ -623,7 +623,7 @@ void Module::add_variables_to_symbols() {
         id->range(rangeKind::range);       // always a range
         id->access(accessKind::readwrite);
 
-        symbols_[var] = symbol_ptr{id};
+        symbols_[var.name()] = symbol_ptr{id};
     }
 
     // add the parameters
diff --git a/modcc/parser.cpp b/modcc/parser.cpp
index 0b4b8106..ffa498c0 100644
--- a/modcc/parser.cpp
+++ b/modcc/parser.cpp
@@ -368,17 +368,35 @@ void Parser::parse_state_block() {
         return;
     }
 
-    // there are no use cases for curly brace in a STATE block, so we don't have to count them
-    // we have to get the next token before entering the loop to handle the case of
-    // an empty block {}
+    // there are no use cases for curly brace in a STATE block, so we don't have
+    // to count them we have to get the next token before entering the loop to
+    // handle the case of an empty block {}
     get_token();
-    while(token_.type!=tok::rbrace) {
+    while(token_.type!=tok::rbrace && token_.type != tok::eof) {
+        int line = location_.line;
+        Id parm;
+
         if(token_.type != tok::identifier) {
-            error(pprintf("'%' is not a valid name for a state variable", token_.spelling));
+            error(pprintf("'%' is not a valid name for a state variable",
+                          token_.spelling));
             return;
         }
-        state_block.state_variables.push_back(token_.spelling);
+
+        parm.token = token_;
+        //state_block.state_variables.push_back(token_.spelling);
         get_token();
+
+        // get unit parameters
+        if (line == location_.line && token_.type == tok::lparen) {
+            parm.units = unit_description();
+            if (status_ == lexerStatus::error) {
+                error(pprintf("STATUS block unexpected symbol '%s'",
+                              token_.spelling));
+                return;
+            }
+        }
+
+        state_block.state_variables.push_back(parm);
     }
 
     // add this state block information to the module
@@ -505,7 +523,7 @@ void Parser::parse_parameter_block() {
 parm_exit:
     // only write error message if one hasn't already been logged by the lexer
     if(!success && status_==lexerStatus::happy) {
-        error(pprintf("PARAMETER block unexpected symbol '%'", token_.spelling));
+        error(pprintf("PARAMETER block unexpected symbol '%s'", token_.spelling));
     }
     return;
 }
diff --git a/tests/modcc/test_parser.cpp b/tests/modcc/test_parser.cpp
index 1e590d83..2d09e570 100644
--- a/tests/modcc/test_parser.cpp
+++ b/tests/modcc/test_parser.cpp
@@ -550,10 +550,11 @@ TEST(Parser, parse_binop) {
         EXPECT_NE(e, nullptr);
         EXPECT_EQ(p.status(), lexerStatus::happy);
 
-        // A loose tolerance of 1d-10 is required here because the eval() function uses long double
-        // for intermediate results (like constant folding in modparser).
-        // For expressions with transcendental operations this can see relatively large divergence between
-        // the double and long double results.
+        // A loose tolerance of 1d-10 is required here because the eval()
+        // function uses long double for intermediate results (like constant
+        // folding in modparser).  For expressions with transcendental
+        // 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
-- 
GitLab