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; +}