diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd50fe6dc623d4aeb7f01343f680741d25bd74f7..5b7a8beba558ddb122fee99e6f381aeff96ebe1d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -93,6 +93,7 @@ include_directories(${CMAKE_SOURCE_DIR}/vector)
 include_directories(${CMAKE_SOURCE_DIR}/include)
 include_directories(${CMAKE_SOURCE_DIR}/src)
 include_directories(${CMAKE_SOURCE_DIR}/miniapp)
+include_directories(${CMAKE_SOURCE_DIR}/modcc/src)
 include_directories(${CMAKE_SOURCE_DIR})
 if( "${WITH_TBB}" STREQUAL "ON" )
     include_directories(${TBB_INCLUDE_DIRS})
diff --git a/data/test.mod b/data/test.mod
new file mode 100644
index 0000000000000000000000000000000000000000..9ab7868500498e2232327d7ec17fbda32ae3dc21
--- /dev/null
+++ b/data/test.mod
@@ -0,0 +1,80 @@
+: sample file
+
+NEURON  {
+    THREADSAFE
+    SUFFIX test
+    USEION ca WRITE ik READ ki, cai
+    RANGE  gkbar, ik, ek, ki, cai
+    GLOBAL minf, mtau, hinf, htau
+}
+
+STATE {
+    h
+    m r
+}
+
+UNITS   {
+    (S) = (siemens)
+    (mV) = (millivolt)
+    (mA) = (milliamp)
+    (molar) = (1/liter)
+    (mM) = (milli/liter)
+}
+
+PARAMETER {
+    cai
+    gkbar   = 0.1  (mho/cm2)
+    celsius
+    ek      = -100 (mV)    : must be explicitly def. in hoc
+    v       (mV)
+    vhalfm  =-43   (mV)
+    km      =8
+    vhalfh  =-67   (mV)
+    kh      =7.3
+    q10     =2.3
+}
+
+ASSIGNED {
+    ki      (mA/cm2)
+    ik      (mA/cm2)
+    minf    mtau (ms)
+    hinf    htau (ms)
+}
+
+BREAKPOINT {
+    SOLVE states METHOD cnexp
+    ik = gkbar * m*h*(v-ek)
+}
+
+INITIAL {
+    trates(v)
+    m=minf
+    h=hinf
+}
+
+PROCEDURE trates(v) {
+    LOCAL qt
+    qt=q10^((celsius-22)/10)
+    minf=1-1/(1+exp((v-vhalfm)/km))
+    hinf=1/(1+exp((v-vhalfh)/kh))
+
+    mtau = 0.6
+    htau = 1500
+}
+
+: the 'states' in the definition is giving the derivative a name
+: this name is then used in the SOLVE statement above
+: should states be a procedure with special declaration syntax (takes no arguments by default)?
+
+DERIVATIVE states {
+    trates(v)
+    m' = (minf-m)/mtau
+    h' = (hinf-h)/htau
+}
+
+FUNCTION okcinf(Vm)  {
+    LOCAL a
+    LOCAL b
+    a = 1.25*(10^8)*(cai)*(cai)
+    okcinf = a/(a+b)
+}
diff --git a/modcc/src/blocks.hpp b/modcc/src/blocks.hpp
index 9fee8ab18e72465d3c7f4f97faffca0e174e84b5..58db08e155343039f03b41577b7887f05480e647 100644
--- a/modcc/src/blocks.hpp
+++ b/modcc/src/blocks.hpp
@@ -6,7 +6,7 @@
 #include "identifier.hpp"
 #include "location.hpp"
 #include "token.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 // describes a relationship with an ion channel
 struct IonDep {
@@ -164,4 +164,3 @@ inline std::ostream& operator<< (std::ostream& os, AssignedBlock const& A) {
 
     return os;
 }
-
diff --git a/modcc/src/cprinter.cpp b/modcc/src/cprinter.cpp
index 93f05a8d1c9fa7777043c8c12b8b55ea98b7ef1e..b1991ea9d52c4f2e227033650bff22f36cb72c48 100644
--- a/modcc/src/cprinter.cpp
+++ b/modcc/src/cprinter.cpp
@@ -891,4 +891,3 @@ void CPrinter::visit(BinaryExpression *e) {
     // reset parent precedence
     parent_op_ = pop;
 }
-
diff --git a/modcc/src/expression.hpp b/modcc/src/expression.hpp
index b37f18552ada9e6c887354a090cee941b6eab01a..b1076b5b4a59e50f12aa936207ac0f3ddf9097bb 100644
--- a/modcc/src/expression.hpp
+++ b/modcc/src/expression.hpp
@@ -11,7 +11,7 @@
 #include "identifier.hpp"
 #include "memop.hpp"
 #include "scope.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 class Visitor;
 
@@ -1155,4 +1155,3 @@ public:
 
     void accept(Visitor *v) override;
 };
-
diff --git a/modcc/src/expressionclassifier.cpp b/modcc/src/expressionclassifier.cpp
index 83532a4710fb906173e2301c7210d011cb396c63..9b06f14fb1bc836feb9a7e0a15fd16d651537b97 100644
--- a/modcc/src/expressionclassifier.cpp
+++ b/modcc/src/expressionclassifier.cpp
@@ -3,7 +3,7 @@
 
 #include "error.hpp"
 #include "expressionclassifier.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 // this turns out to be quite easy, however quite fiddly to do right.
 
@@ -320,4 +320,3 @@ void ExpressionClassifierVisitor::visit(CallExpression *e) {
         }
     }
 }
-
diff --git a/modcc/src/functionexpander.cpp b/modcc/src/functionexpander.cpp
index c2e727fdb33ecb6bc59c73eddf36ba4353263c22..dd32ba7662e952799ec9697c4d3407b853456e62 100644
--- a/modcc/src/functionexpander.cpp
+++ b/modcc/src/functionexpander.cpp
@@ -2,7 +2,7 @@
 
 #include "error.hpp"
 #include "functionexpander.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 ///////////////////////////////////////////////////////////////////////////////
 //  function call site lowering
@@ -162,4 +162,3 @@ lower_function_arguments(std::vector<expression_ptr>& args)
 
     return new_statements;
 }
-
diff --git a/modcc/src/functioninliner.cpp b/modcc/src/functioninliner.cpp
index 514594c1d32def708165c881c3f6b4d1a6800a6b..5e0216b9f354e13a9d9e178912808e079cc0a16b 100644
--- a/modcc/src/functioninliner.cpp
+++ b/modcc/src/functioninliner.cpp
@@ -2,7 +2,7 @@
 
 #include "error.hpp"
 #include "functioninliner.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 #include "errorvisitor.hpp"
 
 expression_ptr inline_function_call(Expression* e)
diff --git a/modcc/src/lexer.cpp b/modcc/src/lexer.cpp
index b3562dae9571165ce7ec18bf5fa4fb9c6bed83d7..8846da84aefad764976342a269081bcfd7fe7748 100644
--- a/modcc/src/lexer.cpp
+++ b/modcc/src/lexer.cpp
@@ -4,7 +4,7 @@
 #include <string>
 
 #include "lexer.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 // helpers for identifying character types
 inline bool in_range(char c, char first, char last) {
@@ -370,4 +370,3 @@ tok Lexer::get_identifier_type(std::string const& identifier) {
     auto pos = keyword_map.find(identifier);
     return pos==keyword_map.end() ? tok::identifier : pos->second;
 }
-
diff --git a/modcc/src/memop.hpp b/modcc/src/memop.hpp
index acfe084a77b1224427aaf427980646da70d698b7..a2819cad103a2ef4b93105eab66335f888b0f6c8 100644
--- a/modcc/src/memop.hpp
+++ b/modcc/src/memop.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "util.hpp"
+#include "modccutil.hpp"
 #include "lexer.hpp"
 
 /// Defines a memory operation that is to performed by an APIMethod.
@@ -35,4 +35,3 @@ struct MemOp {
         }
     }
 };
-
diff --git a/modcc/src/modcc.cpp b/modcc/src/modcc.cpp
index 9161bbfd2f63d6945739fceb2825a7f3ede1dfef..423f855893a8b8fbb606676c58b1a8eb4a57ab41 100644
--- a/modcc/src/modcc.cpp
+++ b/modcc/src/modcc.cpp
@@ -10,7 +10,7 @@
 #include "module.hpp"
 #include "parser.hpp"
 #include "perfvisitor.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 //#define VERBOSE
 
diff --git a/modcc/src/util.hpp b/modcc/src/modccutil.hpp
similarity index 99%
rename from modcc/src/util.hpp
rename to modcc/src/modccutil.hpp
index 00f323f5375637efa2dff0bf9519adf874a7a6ad..80c90d9fc265a9ee0ed86383da919b37da49f352 100644
--- a/modcc/src/util.hpp
+++ b/modcc/src/modccutil.hpp
@@ -133,4 +133,3 @@ template <typename T, typename... Args>
 std::unique_ptr<T> make_unique(Args&&... args) {
     return std::unique_ptr<T>(new T(std::forward<Args>(args) ...));
 }
-
diff --git a/modcc/src/parser.cpp b/modcc/src/parser.cpp
index 30dbb9ea3f72cbd98ae48b3a598275de3e59a3b8..a0884900f2dee855a2ddbc12c47d7306ad278383 100644
--- a/modcc/src/parser.cpp
+++ b/modcc/src/parser.cpp
@@ -6,7 +6,7 @@
 #include "parser.hpp"
 #include "perfvisitor.hpp"
 #include "token.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 // specialize on const char* for lazy evaluation of compile time strings
 bool Parser::expect(tok tok, const char* str) {
@@ -1306,4 +1306,3 @@ expression_ptr Parser::parse_initial() {
 
     return make_expression<InitialBlock>(block_location, std::move(body));
 }
-
diff --git a/modcc/src/scope.hpp b/modcc/src/scope.hpp
index 6e81044f717f9e118f53370d1019a312c0fc8dc0..6a55d6ea982dc6436a9938c60984e46b92347fcf 100644
--- a/modcc/src/scope.hpp
+++ b/modcc/src/scope.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "util.hpp"
+#include "modccutil.hpp"
 
 #include <memory>
 #include <string>
@@ -124,4 +124,3 @@ typename Scope<Symbol>::symbol_map*
 Scope<Symbol>::globals() {
     return global_symbols_;
 }
-
diff --git a/modcc/src/visitor.hpp b/modcc/src/visitor.hpp
index 4a32e5fa8f0bde47ba071058be7f5d601c4d561d..de90fdbc6271bcacef545d5d3f4f635b78c27d22 100644
--- a/modcc/src/visitor.hpp
+++ b/modcc/src/visitor.hpp
@@ -2,7 +2,7 @@
 
 #include "error.hpp"
 #include "expression.hpp"
-#include "util.hpp"
+#include "modccutil.hpp"
 
 /// visitor base class
 /// The visitors for all AST nodes throw an exception
@@ -61,4 +61,3 @@ public:
 
     virtual ~Visitor() {};
 };
-
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c39e22a80f408f824af43f31f1d5e965b1f37ff5..5fd3493290cff53f87d5c1b5a1d6a620d4b16bc4 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,4 +1,3 @@
-
 # google test framework
 add_library(gtest gtest-all.cpp)
 # tests look for gtest.h here
@@ -17,13 +16,14 @@ add_subdirectory(global_communication)
 # Tests for performance: This could include stand alone tests. These do not necessarily be run automatically
 add_subdirectory(performance)
 
+# modcc tests
+add_subdirectory(modcc)
 
 # Proposed additional test types:
 
 # Large test, employing the full simulator. validated using deltas on output data
 
-# Test to check integration between components 
+# Test to check integration between components
 
 
 # Numbered tests based on bugs in the tracker
-
diff --git a/tests/modcc/CMakeLists.txt b/tests/modcc/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d17002a6d7cd7e08e2a1cd9a921462d220660b16
--- /dev/null
+++ b/tests/modcc/CMakeLists.txt
@@ -0,0 +1,36 @@
+set(HEADERS
+	${PROJECT_SOURCE_DIR}/modcc/src/constantfolder.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/cprinter.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/expressionclassifier.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/lexer.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/module.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/perfvisitor.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/parser.hpp
+	${PROJECT_SOURCE_DIR}/src/util.hpp
+	${PROJECT_SOURCE_DIR}/modcc/src/modccutil.hpp
+#	${PROJECT_SOURCE_DIR}/modcc/src/variablerenamer.hpp
+	test.hpp
+)
+
+set(MODCC_TEST_SOURCES
+	# unit tests
+	test_lexer.cpp
+	test_module.cpp
+	test_optimization.cpp
+	test_parser.cpp
+	#test_printers.cpp
+	test_visitors.cpp
+
+	# unit test driver
+	driver.cpp
+)
+
+add_definitions("-DDATADIR=\"${CMAKE_SOURCE_DIR}/data\"")
+add_executable(test_modcc ${MODCC_TEST_SOURCES} ${HEADERS})
+
+target_link_libraries(test_modcc LINK_PUBLIC compiler gtest)
+
+set_target_properties(test_modcc
+	PROPERTIES
+	RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"
+)
diff --git a/tests/modcc/driver.cpp b/tests/modcc/driver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f32ac59d1471f94c6fb3d7d6333eeaf86d2e1c28
--- /dev/null
+++ b/tests/modcc/driver.cpp
@@ -0,0 +1,11 @@
+/**************************************************************
+ * unit test driver
+ **************************************************************/
+
+#include "test.hpp"
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
+
diff --git a/tests/modcc/test.hpp b/tests/modcc/test.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e0b99219420ce4a36e744d4d2c9ce101e9d79d2f
--- /dev/null
+++ b/tests/modcc/test.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gtest.h>
+
+#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
+
+static expression_ptr parse_line_expression(std::string const& s) {
+    return Parser(s).parse_line_expression();
+}
+
+static expression_ptr parse_expression(std::string const& s) {
+    return Parser(s).parse_expression();
+}
+
+static expression_ptr parse_function(std::string const& s) {
+    return Parser(s).parse_function();
+}
+
+static expression_ptr parse_procedure(std::string const& s) {
+    return Parser(s).parse_procedure();
+}
diff --git a/tests/modcc/test_lexer.cpp b/tests/modcc/test_lexer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dacfe8c6e910f6beea2d44e0eb715a4c6fb1b672
--- /dev/null
+++ b/tests/modcc/test_lexer.cpp
@@ -0,0 +1,262 @@
+#include <cmath>
+
+#include "test.hpp"
+#include "lexer.hpp"
+
+//#define PRINT_LEX_STRING std::cout << "________________\n" << string << "\n________________\n";
+#define PRINT_LEX_STRING
+
+/**************************************************************
+ * lexer tests
+ **************************************************************/
+// test identifiers
+TEST(Lexer, identifiers) {
+    char string[] = "_foo:\nbar, buzz f_zz";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::identifier);
+    EXPECT_EQ(t1.spelling, "_foo");
+    // odds are _foo will never be a keyword
+    EXPECT_EQ(is_keyword(t1), false);
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::identifier);
+    EXPECT_EQ(t2.spelling, "bar");
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::comma);
+
+    auto t4 = lexer.parse();
+    EXPECT_EQ(t4.type, tok::identifier);
+    EXPECT_EQ(t4.spelling, "buzz");
+
+    auto t5 = lexer.parse();
+    EXPECT_EQ(t5.type, tok::identifier);
+    EXPECT_EQ(t5.spelling, "f_zz");
+
+    auto t6 = lexer.parse();
+    EXPECT_EQ(t6.type, tok::eof);
+}
+
+// test keywords
+TEST(Lexer, keywords) {
+    char string[] = "NEURON UNITS SOLVE else TITLE CONDUCTANCE";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    // should skip all white space and go straight to eof
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::neuron);
+    EXPECT_EQ(is_keyword(t1), true);
+    EXPECT_EQ(t1.spelling, "NEURON");
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::units);
+    EXPECT_EQ(t2.spelling, "UNITS");
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::solve);
+    EXPECT_EQ(t3.spelling, "SOLVE");
+
+    auto t4 = lexer.parse();
+    EXPECT_EQ(t4.type, tok::else_stmt);
+    EXPECT_EQ(t4.spelling, "else");
+
+    auto t5 = lexer.parse();
+    EXPECT_NE(t5.type, tok::identifier);
+    EXPECT_EQ(t5.spelling, "TITLE");
+
+    auto t7 = lexer.parse();
+    EXPECT_EQ(t7.type, tok::conductance);
+    EXPECT_EQ(t7.spelling, "CONDUCTANCE");
+
+    auto t6 = lexer.parse();
+    EXPECT_EQ(t6.type, tok::eof);
+}
+
+// test white space
+TEST(Lexer, whitespace) {
+    char string[] = " \t\v\f";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    // should skip all white space and go straight to eof
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::eof);
+}
+
+// test new line
+TEST(Lexer, newline) {
+    char string[] = "foo \n    bar \n +\r\n-";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    // get foo
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::identifier);
+    EXPECT_EQ(t1.spelling, "foo");
+    EXPECT_EQ(t1.location.line, 1);
+    EXPECT_EQ(t1.location.column, 1);
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::identifier);
+    EXPECT_EQ(t2.spelling, "bar");
+    EXPECT_EQ(t2.location.line, 2);
+    EXPECT_EQ(t2.location.column, 5);
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::plus);
+    EXPECT_EQ(t3.spelling, "+");
+    EXPECT_EQ(t3.location.line, 3);
+    EXPECT_EQ(t3.location.column, 2);
+
+    // test for carriage return + newline, i.e. \r\n
+    auto t4 = lexer.parse();
+    EXPECT_EQ(t4.type, tok::minus);
+    EXPECT_EQ(t4.spelling, "-");
+    EXPECT_EQ(t4.location.line, 4);
+    EXPECT_EQ(t4.location.column, 1);
+}
+
+// test operators
+TEST(Lexer, symbols) {
+    char string[] = "+-/*, t= ^ h'";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::plus);
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::minus);
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::divide);
+
+    auto t4 = lexer.parse();
+    EXPECT_EQ(t4.type, tok::times);
+
+    auto t5 = lexer.parse();
+    EXPECT_EQ(t5.type, tok::comma);
+
+    // test that identifier followed by = is parsed correctly
+    auto t6 = lexer.parse();
+    EXPECT_EQ(t6.type, tok::identifier);
+
+    auto t7 = lexer.parse();
+    EXPECT_EQ(t7.type, tok::eq);
+
+    auto t8 = lexer.parse();
+    EXPECT_EQ(t8.type, tok::pow);
+
+    auto t9 = lexer.parse();
+    EXPECT_EQ(t9.type, tok::identifier);
+
+    // check that prime' is parsed properly after symbol
+    // as this is how it is used to indicate a derivative
+    auto t10 = lexer.parse();
+    EXPECT_EQ(t10.type, tok::prime);
+
+    auto t11 = lexer.parse();
+    EXPECT_EQ(t11.type, tok::eof);
+}
+
+TEST(Lexer, comparison_operators) {
+    char string[] = "< <= > >= == != !";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::lt);
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::lte);
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::gt);
+    auto t4 = lexer.parse();
+    EXPECT_EQ(t4.type, tok::gte);
+    auto t5 = lexer.parse();
+    EXPECT_EQ(t5.type, tok::equality);
+    auto t6 = lexer.parse();
+    EXPECT_EQ(t6.type, tok::ne);
+    auto t7 = lexer.parse();
+    EXPECT_EQ(t7.type, tok::lnot);
+
+    auto t8 = lexer.parse();
+    EXPECT_EQ(t8.type, tok::eof);
+}
+
+// test braces
+TEST(Lexer, braces) {
+    char string[] = "foo}";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::identifier);
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::rbrace);
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::eof);
+}
+
+// test comments
+TEST(Lexer, comments) {
+    char string[] = "foo:this is one line\nbar : another comment\n";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::identifier);
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::identifier);
+    EXPECT_EQ(t2.spelling, "bar");
+    EXPECT_EQ(t2.location.line, 2);
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::eof);
+}
+
+// test numbers
+TEST(Lexer, numbers) {
+    char string[] = "1 .3 23 87.99 12. -3";
+    PRINT_LEX_STRING
+    Lexer lexer(string, string+sizeof(string));
+
+    auto t1 = lexer.parse();
+    EXPECT_EQ(t1.type, tok::number);
+    EXPECT_EQ(std::stod(t1.spelling), 1.0);
+
+    auto t2 = lexer.parse();
+    EXPECT_EQ(t2.type, tok::number);
+    EXPECT_EQ(std::stod(t2.spelling), 0.3);
+
+    auto t3 = lexer.parse();
+    EXPECT_EQ(t3.type, tok::number);
+    EXPECT_EQ(std::stod(t3.spelling), 23.0);
+
+    auto t4 = lexer.parse();
+    EXPECT_EQ(t4.type, tok::number);
+    EXPECT_EQ(std::stod(t4.spelling), 87.99);
+
+    auto t5 = lexer.parse();
+    EXPECT_EQ(t5.type, tok::number);
+    EXPECT_EQ(std::stod(t5.spelling), 12.0);
+
+    // the lexer does not decide where the - sign goes
+    // the parser uses additional contextual information to
+    // decide if the minus is a binary or unary expression
+    auto t6 = lexer.parse();
+    EXPECT_EQ(t6.type, tok::minus);
+
+    auto t7 = lexer.parse();
+    EXPECT_EQ(t7.type, tok::number);
+    EXPECT_EQ(std::stod(t7.spelling), 3.0);
+
+    auto t8 = lexer.parse();
+    EXPECT_EQ(t8.type, tok::eof);
+}
diff --git a/tests/modcc/test_module.cpp b/tests/modcc/test_module.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..74fa541b71c8911e30f3aa68fd17671549d57195
--- /dev/null
+++ b/tests/modcc/test_module.cpp
@@ -0,0 +1,16 @@
+#include "test.hpp"
+#include "module.hpp"
+
+TEST(Module, open) {
+    Module m(DATADIR "/test.mod");
+    if(!m.buffer().size()) {
+        std::cout << "skipping Module.open test because unable to open input file" << std::endl;
+        return;
+    }
+    Lexer lexer(m.buffer());
+    auto t = lexer.parse();
+    while(t.type != tok::eof) {
+        t = lexer.parse();
+        EXPECT_NE(t.type, tok::reserved);
+    }
+}
diff --git a/tests/modcc/test_optimization.cpp b/tests/modcc/test_optimization.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9b69cee7d2aa5c16af82e2a143cc9632c9b359b5
--- /dev/null
+++ b/tests/modcc/test_optimization.cpp
@@ -0,0 +1,80 @@
+#include <cmath>
+
+#include "test.hpp"
+
+#include "constantfolder.hpp"
+#include "modccutil.hpp"
+
+TEST(Optimizer, constant_folding) {
+    auto v = make_unique<ConstantFolderVisitor>();
+    {
+        auto e = parse_line_expression("x = 2*3");
+        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( "" )
+    }
+    {
+        auto e = parse_line_expression("x = 1 + 2 + 3");
+        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( "" )
+    }
+    {
+        auto e = parse_line_expression("x = exp(2)");
+        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
+        // the 16 decimal place to the double precision value from std::exp(2.0).
+        // 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( "" )
+    }
+    {
+        auto e = parse_line_expression("x= 2*2 + 3");
+        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( "" )
+    }
+    {
+        auto e = parse_line_expression("x= 3 + 2*2");
+        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( "" )
+    }
+    {
+        // this doesn't work: the (y+2) expression is not a constant, so folding stops.
+        // we need to fold the 2+3, which isn't possible with a simple walk.
+        // 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() )
+        e->accept(v.get());
+        VERBOSE_PRINT( e->to_string() )
+        VERBOSE_PRINT( "" )
+    }
+    {
+        auto e = parse_line_expression("x= 2 + 3 + y");
+        VERBOSE_PRINT( e->to_string() )
+        e->accept(v.get());
+        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() )
+        e->accept(v.get());
+        VERBOSE_PRINT( e->to_string() )
+        VERBOSE_PRINT("");
+    }
+}
diff --git a/tests/modcc/test_parser.cpp b/tests/modcc/test_parser.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1e590d8315a5d95b3e93555d5ddbd2dbd9ad00be
--- /dev/null
+++ b/tests/modcc/test_parser.cpp
@@ -0,0 +1,563 @@
+#include <cmath>
+
+#include "test.hpp"
+#include "module.hpp"
+#include "parser.hpp"
+
+TEST(Parser, full_file) {
+    Module m(DATADIR "/test.mod");
+    if(m.buffer().size()==0) {
+        std::cout << "skipping Parser.full_file test because unable to open input file" << std::endl;
+        return;
+    }
+    Parser p(m);
+    EXPECT_EQ(p.status(), lexerStatus::happy);
+}
+
+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;
+        }
+    }
+}
+
+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;
+    }
+}
+
+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;
+        }
+    }
+}
+
+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");
+        }
+
+        // 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("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;
+        }
+    }
+}
+
+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");
+        }
+
+        // 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("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;
+        }
+    }
+}
+
+TEST(Parser, parse_if) {
+    {
+        char expression[] =
+        "   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;
+        }
+    }
+    {
+        char expression[] =
+        "   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;
+        }
+    }
+    {
+        char expression[] =
+        "   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;
+        }
+    }
+}
+
+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);
+        }
+
+        // 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());
+        }
+
+        // always print the compiler errors, because they are unexpected
+        if(p.status()==lexerStatus::error)
+            std::cout << red("error") << p.error_message() << std::endl;
+    }
+
+    ////////////////////// test for invalid expressions //////////////////////
+    {
+        Parser p("LOCAL 2");
+        auto e = p.parse_local();
+
+        EXPECT_EQ(e, nullptr);
+        EXPECT_EQ(p.status(), lexerStatus::error);
+
+        #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
+    }
+
+    {
+        Parser p("LOCAL x, ");
+        auto e = p.parse_local();
+
+        EXPECT_EQ(e, nullptr);
+        EXPECT_EQ(p.status(), lexerStatus::error);
+
+        #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
+    }
+}
+
+TEST(Parser, parse_unary_expression) {
+    std::vector<const char*> good_expressions =
+    {
+"+x             ",
+"-x             ",
+"(x + -y)       ",
+"-(x - + -y)    ",
+"exp(x + y)     ",
+"-exp(x + -y)   ",
+    };
+
+    for(auto const& expression : good_expressions) {
+        Parser p(expression);
+        auto e = p.parse_unaryop();
+
+#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;
+    }
+}
+
+// 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))",
+    };
+
+    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 const& expression : bad_expressions) {
+        Parser p(expression);
+        auto e = p.parse_parenthesis_expression();
+
+        EXPECT_EQ(e, nullptr);
+        EXPECT_EQ(p.status(), lexerStatus::error);
+
+#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
+    }
+}
+
+// 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"
+    };
+
+    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 const& expression : bad_expressions) {
+        Parser p(expression);
+        auto e = p.parse_line_expression();
+
+        EXPECT_EQ(e, nullptr);
+        EXPECT_EQ(p.status(), lexerStatus::error);
+
+#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
+    }
+}
+
+long double eval(Expression *e) {
+    if(auto n = e->is_number()) {
+        return n->value();
+    }
+    if(auto b = e->is_binary()) {
+        auto lhs = eval(b->lhs());
+        auto rhs = eval(b->rhs());
+        switch(b->op()) {
+            case tok::plus  : return lhs+rhs;
+            case tok::minus : return lhs-rhs;
+            case tok::times : return lhs*rhs;
+            case tok::divide: return lhs/rhs;
+            case tok::pow   : return std::pow(lhs,rhs);
+            default:;
+        }
+    }
+    if(auto u = e->is_unary()) {
+        auto val = eval(u->expression());
+        switch(u->op()) {
+            case tok::plus  : return  val;
+            case tok::minus : return -val;
+            default:;
+        }
+    }
+    return std::numeric_limits<long double>::quiet_NaN();
+}
+
+// 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 =
+    {
+        // simple
+        {"2+3", 2.+3.},
+        {"2-3", 2.-3.},
+        {"2*3", 2.*3.},
+        {"2/3", 2./3.},
+        {"2^3", std::pow(2., 3.)},
+
+        // more complicated
+        {"2+3*2", 2.+(3*2)},
+        {"2*3-5", (2.*3)-5.},
+        {"2+3*(-2)", 2.+(3*-2)},
+        {"2+3*(-+2)", 2.+(3*-+2)},
+        {"2/3*4", (2./3.)*4.},
+
+        // right associative
+        {"2^3^1.5", std::pow(2.,std::pow(3.,1.5))},
+        {"2^3^1.5^2", std::pow(2.,std::pow(3.,std::pow(1.5,2.)))},
+        {"2^2^3", std::pow(2.,std::pow(2.,3.))},
+        {"(2^2)^3", std::pow(std::pow(2.,2.),3.)},
+        {"3./2^7.", 3./std::pow(2.,7.)},
+        {"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);
+
+        // 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
+        if(p.status()==lexerStatus::error)
+            std::cout << red("error") << p.error_message() << std::endl;
+    }
+}
diff --git a/tests/modcc/test_printers.cpp b/tests/modcc/test_printers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dad3a71911de27e243112678b82698d5b792ad51
--- /dev/null
+++ b/tests/modcc/test_printers.cpp
@@ -0,0 +1,82 @@
+#include "test.hpp"
+
+#include "cprinter.hpp"
+
+using scope_type = Scope<Symbol>;
+using symbol_map = scope_type::symbol_map;
+using symbol_ptr = Scope<Symbol>::symbol_ptr;
+
+TEST(CPrinter, statement) {
+    std::vector<const char*> expressions =
+    {
+"y=x+3",
+"y=y^z",
+"y=exp(x/2 + 3)",
+    };
+
+    // create a scope that contains the symbols used in the tests
+    Scope<Symbol>::symbol_map globals;
+    globals["x"] = make_symbol<Symbol>(Location(), "x", symbolKind::local);
+    globals["y"] = make_symbol<Symbol>(Location(), "y", symbolKind::local);
+    globals["z"] = make_symbol<Symbol>(Location(), "z", symbolKind::local);
+
+    auto scope = std::make_shared<Scope<Symbol>>(globals);
+
+    for(auto const& expression : expressions) {
+        auto e = parse_line_expression(expression);
+
+        // sanity check the compiler
+        EXPECT_NE(e, nullptr);
+        if( e==nullptr ) continue;
+
+        e->semantic(scope);
+        auto v = make_unique<CPrinter>();
+        e->accept(v.get());
+
+#ifdef VERBOSE_TEST
+        std::cout << e->to_string() << std::endl;
+                  << " :--: " << v->text() << std::endl;
+#endif
+    }
+}
+
+TEST(CPrinter, proc) {
+    std::vector<const char*> expressions =
+    {
+"PROCEDURE trates(v) {\n"
+"    LOCAL k\n"
+"    minf=1-1/(1+exp((v-k)/k))\n"
+"    hinf=1/(1+exp((v-k)/k))\n"
+"    mtau = 0.6\n"
+"    htau = 1500\n"
+"}"
+    };
+
+    // create a scope that contains the symbols used in the tests
+    Scope<Symbol>::symbol_map globals;
+    globals["minf"] = make_symbol<VariableExpression>(Location(), "minf");
+    globals["hinf"] = make_symbol<VariableExpression>(Location(), "hinf");
+    globals["mtau"] = make_symbol<VariableExpression>(Location(), "mtau");
+    globals["htau"] = make_symbol<VariableExpression>(Location(), "htau");
+    globals["v"]    = make_symbol<VariableExpression>(Location(), "v");
+
+    for(auto const& expression : expressions) {
+        auto e = symbol_ptr{parse_procedure(expression)->is_symbol()};
+
+        // sanity check the compiler
+        EXPECT_NE(e, nullptr);
+
+        if( e==nullptr ) continue;
+
+        globals["trates"] = std::move(e);
+
+        e->semantic(globals);
+        auto v = make_unique<CPrinter>();
+        e->accept(v.get());
+
+#ifdef VERBOSE_TEST
+        std::cout << e->to_string() << std::endl;
+                  << " :--: " << v->text() << std::endl;
+#endif
+    }
+}
diff --git a/tests/modcc/test_visitors.cpp b/tests/modcc/test_visitors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5c084e0646d032da09302793190ebde68a8df5e
--- /dev/null
+++ b/tests/modcc/test_visitors.cpp
@@ -0,0 +1,302 @@
+#include "test.hpp"
+
+#include "constantfolder.hpp"
+#include "expressionclassifier.hpp"
+#include "perfvisitor.hpp"
+#include "parser.hpp"
+#include "modccutil.hpp"
+
+/**************************************************************
+ * visitors
+ **************************************************************/
+
+TEST(FlopVisitor, basic) {
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("x+y");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("x-y");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("x*y");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.mul, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("x/y");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.div, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("exp(x)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.exp, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("log(x)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.log, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("cos(x)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.cos, 1);
+    }
+
+    {
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("sin(x)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.sin, 1);
+    }
+}
+
+TEST(FlopVisitor, compound) {
+    {
+        auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("x+y*z/a-b");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 2);
+    EXPECT_EQ(visitor->flops.mul, 1);
+    EXPECT_EQ(visitor->flops.div, 1);
+    }
+
+    {
+        auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("exp(x+y+z)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 2);
+    EXPECT_EQ(visitor->flops.exp, 1);
+    }
+
+    {
+        auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_expression("exp(x+y) + 3/(12 + z)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 3);
+    EXPECT_EQ(visitor->flops.div, 1);
+    EXPECT_EQ(visitor->flops.exp, 1);
+    }
+
+    // test asssignment expression
+    {
+        auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_line_expression("x = exp(x+y) + 3/(12 + z)");
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 3);
+    EXPECT_EQ(visitor->flops.div, 1);
+    EXPECT_EQ(visitor->flops.exp, 1);
+    }
+}
+
+TEST(FlopVisitor, procedure) {
+    {
+    const char *expression =
+"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"
+"}";
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_procedure(expression);
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 6);
+    EXPECT_EQ(visitor->flops.neg, 0);
+    EXPECT_EQ(visitor->flops.mul, 0);
+    EXPECT_EQ(visitor->flops.div, 5);
+    EXPECT_EQ(visitor->flops.exp, 2);
+    EXPECT_EQ(visitor->flops.pow, 1);
+    }
+}
+
+TEST(FlopVisitor, function) {
+    {
+    const char *expression =
+"FUNCTION foo(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"
+"    foo = minf + hinf\n"
+"}";
+    auto visitor = make_unique<FlopVisitor>();
+    auto e = parse_function(expression);
+    e->accept(visitor.get());
+    EXPECT_EQ(visitor->flops.add, 7);
+    EXPECT_EQ(visitor->flops.neg, 1);
+    EXPECT_EQ(visitor->flops.mul, 0);
+    EXPECT_EQ(visitor->flops.div, 5);
+    EXPECT_EQ(visitor->flops.exp, 2);
+    EXPECT_EQ(visitor->flops.pow, 1);
+    }
+}
+
+TEST(ClassificationVisitor, linear) {
+    std::vector<const char*> expressions =
+    {
+"x + y + z",
+"y + x + z",
+"y + z + x",
+"x - y - z",
+"y - x - z",
+"y - z - x",
+"z*(x + y + 2)",
+"(x + y)*z",
+"(x + y)/z",
+"x+3",
+"-x",
+"x+x+x+x",
+"2*x     ",
+"y*x     ",
+"x + y   ",
+"y + x   ",
+"y + z*x ",
+"2*(x*z + y)",
+"z*x - y",
+"(2+z)*(x*z + y)",
+"x/y",
+"(y - x)/z",
+"(x - y)/z",
+"y*(x - y)/z",
+    };
+
+    // create a scope that contains the symbols used in the tests
+    Scope<Symbol>::symbol_map globals;
+    globals["x"] = make_symbol<LocalVariable>(Location(), "x");
+    globals["y"] = make_symbol<LocalVariable>(Location(), "y");
+    globals["z"] = make_symbol<LocalVariable>(Location(), "z");
+    auto x = globals["x"].get();
+
+    auto scope = std::make_shared<Scope<Symbol>>(globals);
+
+    for(auto const& expression : expressions) {
+        auto e = parse_expression(expression);
+
+        // sanity check the compiler
+        EXPECT_NE(e, nullptr);
+        if( e==nullptr ) continue;
+
+        e->semantic(scope);
+        auto v = new ExpressionClassifierVisitor(x);
+        e->accept(v);
+        //std::cout << "expression " << e->to_string() << std::endl;
+        //std::cout << "linear     " << v->linear_coefficient()->to_string() << std::endl;
+        //std::cout << "constant   " << v->constant_term()->to_string() << std::endl;
+        EXPECT_EQ(v->classify(), expressionClassification::linear);
+
+#ifdef VERBOSE_TEST
+        std::cout << "eq    "   << e->to_string()
+                  << "\ncoeff " << v->linear_coefficient()->to_string()
+                  << "\nconst " << v-> constant_term()->to_string()
+                  << "\n----"   << std::endl;
+#endif
+        delete v;
+    }
+}
+
+TEST(ClassificationVisitor, constant) {
+    std::vector<const char*> expressions =
+    {
+"y+3",
+"-y",
+"exp(y+z)",
+"1",
+"y^z",
+    };
+
+    // create a scope that contains the symbols used in the tests
+    Scope<Symbol>::symbol_map globals;
+    globals["x"] = make_symbol<LocalVariable>(Location(), "x");
+    globals["y"] = make_symbol<LocalVariable>(Location(), "y");
+    globals["z"] = make_symbol<LocalVariable>(Location(), "z");
+    auto scope = std::make_shared<Scope<Symbol>>(globals);
+    auto x = globals["x"].get();
+
+    for(auto const& expression : expressions) {
+        auto e = parse_expression(expression);
+
+        // sanity check the compiler
+        EXPECT_NE(e, nullptr);
+        if( e==nullptr ) continue;
+
+        e->semantic(scope);
+        auto v = new ExpressionClassifierVisitor(x);
+        e->accept(v);
+        EXPECT_EQ(v->classify(), expressionClassification::constant);
+
+#ifdef VERBOSE_TEST
+        if(e) std::cout << e->to_string() << std::endl;
+        if(p.status()==lexerStatus::error)
+            std::cout << "in " << colorize(expression, kCyan) << "\t" << p.error_message() << std::endl;
+#endif
+        delete v;
+    }
+}
+
+TEST(ClassificationVisitor, nonlinear) {
+    std::vector<const char*> expressions =
+    {
+"x*x",
+"x*2*x",
+"x*(2+x)",
+"y/x",
+"x*(y + z*(x/y))",
+"exp(x)",
+"exp(x+y)",
+"exp(z*(x+y))",
+"log(x)",
+"cos(x)",
+"sin(x)",
+"x^y",
+"y^x",
+    };
+
+    // create a scope that contains the symbols used in the tests
+    Scope<Symbol>::symbol_map globals;
+    globals["x"] = make_symbol<LocalVariable>(Location(), "x");
+    globals["y"] = make_symbol<LocalVariable>(Location(), "y");
+    globals["z"] = make_symbol<LocalVariable>(Location(), "z");
+    auto scope = std::make_shared<Scope<Symbol>>(globals);
+    auto x = globals["x"].get();
+
+    auto v = new ExpressionClassifierVisitor(x);
+    for(auto const& expression : expressions) {
+        auto e = parse_expression(expression);
+
+        // sanity check the compiler
+        EXPECT_NE(e, nullptr);
+        if( e==nullptr ) continue;
+
+        e->semantic(scope);
+        v->reset();
+        e->accept(v);
+        EXPECT_EQ(v->classify(), expressionClassification::nonlinear);
+
+#ifdef VERBOSE_TEST
+        if(e) std::cout << e->to_string() << std::endl;
+        if(p.status()==lexerStatus::error)
+            std::cout << "in " << colorize(expression, kCyan) << "\t" << p.error_message() << std::endl;
+#endif
+    }
+    delete v;
+}